From 6a8912e02a788557c1f361604d4706e3167e17aa Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Mon, 25 Jan 2021 22:00:10 -0800 Subject: [PATCH 01/31] Use fnGetAtt instead of ref --- samtranslator/model/lambda_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index 0cb75661ce..c6f2baaa18 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -52,7 +52,7 @@ class LambdaAlias(Resource): "ProvisionedConcurrencyConfig": PropertyType(False, is_type(dict)), } - runtime_attrs = {"arn": lambda self: ref(self.logical_id)} + runtime_attrs = {"arn": lambda self: fnGetAtt(self.logical_id, "Arn")} class LambdaEventSourceMapping(Resource): From 568666ed9c9921fd130d3e4bef6c2752b67e7656 Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Tue, 26 Jan 2021 08:38:05 -0800 Subject: [PATCH 02/31] Return gracefully --- samtranslator/model/lambda_.py | 2 +- samtranslator/open_api/open_api.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index c6f2baaa18..0cb75661ce 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -52,7 +52,7 @@ class LambdaAlias(Resource): "ProvisionedConcurrencyConfig": PropertyType(False, is_type(dict)), } - runtime_attrs = {"arn": lambda self: fnGetAtt(self.logical_id, "Arn")} + runtime_attrs = {"arn": lambda self: ref(self.logical_id)} class LambdaEventSourceMapping(Resource): diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index b31f3b7527..8dea2aa520 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -101,7 +101,14 @@ def get_integration_function_logical_id(self, path_name, method_name): # Extract lambda integration (${LambdaName.Arn}) and split ".Arn" off from it regex = "([A-Za-z0-9]+\.Arn)" - match = re.findall(regex, arn)[0].split(".Arn")[0] + matches = re.findall(regex, arn) + # For Functions with AutoPublishAlias the integration URI will contain + # ${LambdaName}, not ${LambdaName.Arn} (both resolve to the AWS::Lambda::Alias ARN). + # Without the .Arn suffix, parsing the URI is more ambiguous, so instead of + # introducing more brittle hacks, return gracefully. + if not matches: + return False + match = matches[0].split(".Arn")[0] return match def method_has_integration(self, method): From f705e03beab50fb9abf1b67886835efe5cc7f79a Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Fri, 20 Nov 2020 12:15:29 -0800 Subject: [PATCH 03/31] chore: update develop with latest release branch (#1822) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: bump version 1.30.0 (#1792) * Fix conflicts release 1 30 (#1804) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) * Fix merge conflict in release 1.29.0 (#1771) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * Release/v1.29.0 (#1769) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * fix: Validate API request models (#1757) * chore: Upgrade outdated dependencies in base.txt and dev.txt (#1744) * chore: Update versions in in base.txt (no effective difference) * chore: Update versions specified with ">=" in dev.txt * Fallback pytest to 4.6.x for python2 * chore: Use Compatible release clause in requirement files (#1762) * Fix: SAM Crashes Invalid swagger models exception (#1765) * Fix: Invalid swagger models exception * Fix: Invalid resource policy exception Co-authored-by: Mufaddal Makati * Fix: DefaultAuth not a string exception (#1774) * Fix: DefaultAuth not a string exception * fix: userpool ref not a string Co-authored-by: Mufaddal Makati * Adding PermissionsBoundary property for State Machine resource (#1772) * Adding PermissionsBoundary property for State Machine resource * Add permissions_boundary to StateMachineGenerator and _construct_role docstrings Co-authored-by: Vaib Suri * fix: use newer policy name in gov & cn regions for xray (#1767) * Add Description property to Api and HttApi resources (#1719) * Update DEVELOPMENT_GUIDE; moved from rst to md (#1778) * chore: bump version 1.30.0 (#1792) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Anton Grübel * Revert boto3 dependency pin to ~=1.5 (#1810) (#1812) * Revert boto3 dependency pin to ~=1.5 (#1810) * Move Tests to Appveyor (#1801) * print python version * update path vars * update linux cmd * update linux cmd * update linux cmd * update whitelist in tox * update passenv * update tox whitelisting * update tox whitelisting Co-authored-by: javulticat <31746850+javulticat@users.noreply.github.com> Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update __init__.py (#1813) * Fix merge conflicts 1.30.1 (#1815) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) * Fix merge conflict in release 1.29.0 (#1771) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * Release/v1.29.0 (#1769) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * Release 1.30.0 (#1808) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * fix: Validate API request models (#1757) * Backward merge master branch into develop (#1761) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo * chore: Upgrade outdated dependencies in base.txt and dev.txt (#1744) * chore: Update versions in in base.txt (no effective difference) * chore: Update versions specified with ">=" in dev.txt * Fallback pytest to 4.6.x for python2 * chore: Use Compatible release clause in requirement files (#1762) * Fix: SAM Crashes Invalid swagger models exception (#1765) * Fix: Invalid swagger models exception * Fix: Invalid resource policy exception Co-authored-by: Mufaddal Makati * Fix: DefaultAuth not a string exception (#1774) * Fix: DefaultAuth not a string exception * fix: userpool ref not a string Co-authored-by: Mufaddal Makati * Adding PermissionsBoundary property for State Machine resource (#1772) * Adding PermissionsBoundary property for State Machine resource * Add permissions_boundary to StateMachineGenerator and _construct_role docstrings Co-authored-by: Vaib Suri * fix: use newer policy name in gov & cn regions for xray (#1767) * Update __init__.py (#1775) * Release/v1.29.0 (#1769) (#1773) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * Add Description property to Api and HttApi resources (#1719) * Update DEVELOPMENT_GUIDE; moved from rst to md (#1778) * chore: bump version 1.30.0 (#1792) * Fix conflicts release 1 30 (#1804) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) * Fix merge conflict in release 1.29.0 (#1771) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * Release/v1.29.0 (#1769) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * fix: Validate API request models (#1757) * chore: Upgrade outdated dependencies in base.txt and dev.txt (#1744) * chore: Update versions in in base.txt (no effective difference) * chore: Update versions specified with ">=" in dev.txt * Fallback pytest to 4.6.x for python2 * chore: Use Compatible release clause in requirement files (#1762) * Fix: SAM Crashes Invalid swagger models exception (#1765) * Fix: Invalid swagger models exception * Fix: Invalid resource policy exception Co-authored-by: Mufaddal Makati * Fix: DefaultAuth not a string exception (#1774) * Fix: DefaultAuth not a string exception * fix: userpool ref not a string Co-authored-by: Mufaddal Makati * Adding PermissionsBoundary property for State Machine resource (#1772) * Adding PermissionsBoundary property for State Machine resource * Add permissions_boundary to StateMachineGenerator and _construct_role docstrings Co-authored-by: Vaib Suri * fix: use newer policy name in gov & cn regions for xray (#1767) * Add Description property to Api and HttApi resources (#1719) * Update DEVELOPMENT_GUIDE; moved from rst to md (#1778) * chore: bump version 1.30.0 (#1792) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Anton Grübel * Move Tests to Appveyor (#1801) * print python version * update path vars * update linux cmd * update linux cmd * update linux cmd * update whitelist in tox * update passenv * update tox whitelisting * update tox whitelisting Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Anton Grübel Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Anton Grübel Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Anton Grübel Co-authored-by: Qingchuan Ma <69653965+qingchm@users.noreply.github.com> Co-authored-by: javulticat <31746850+javulticat@users.noreply.github.com> --- samtranslator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index c347ac29ba..4963e4383f 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.29.0" +__version__ = "1.30.1" From 5d0e6452514d20c2d5e3cab1ff4ddcc7f18c64f2 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Mon, 23 Nov 2020 12:02:38 -0800 Subject: [PATCH 04/31] feature: Support for Lambda Code Signing (#53) (#1825) (#1828) * feature: add support for CFN fields for lambda signing (#53) * feature: add support for CFN fields for lambda signing * feature: add support for CFN fields for lambda signing (update formatting) * feature: add support for CFN fields for lambda signing (update patching) * feature: add support for CFN fields for lambda signing (update template) * Revert "feat: add explicit UpdateReplacePolicy (#1481)" (#1568) * docs: document IpV6 option on Domain Configuration object (#1588) * chore: Exclude test modules in whl (#1597) * feat: Add Step Function Resource (#1601) Co-authored-by: Jacob Fuss * Release Changes for 1.25.0 * feature: add support for CFN fields for lambda signing * feature: add support for CFN fields for lambda signing (slight code update) * feature: add support for CFN fields for lambda signing (update globals.py) Co-authored-by: Shreya Co-authored-by: Timo Schilling Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood * Move Tests to Appveyor (#1801) * print python version * update path vars * update linux cmd * update linux cmd * update linux cmd * update whitelist in tox * update passenv * update tox whitelisting * update tox whitelisting Co-authored-by: Shreya Co-authored-by: Timo Schilling Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Shreya Co-authored-by: Timo Schilling Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood --- samtranslator/model/lambda_.py | 1 + samtranslator/model/sam_resources.py | 3 + samtranslator/plugins/globals/globals.py | 1 + .../input/function_with_signing_profile.yaml | 24 ++++++ .../aws-cn/function_with_signing_profile.json | 85 +++++++++++++++++++ .../function_with_signing_profile.json | 85 +++++++++++++++++++ .../error_globals_unsupported_property.json | 4 +- .../output/function_with_signing_profile.json | 85 +++++++++++++++++++ tests/translator/test_function_resources.py | 31 +++++++ tests/translator/test_translator.py | 1 + 10 files changed, 318 insertions(+), 2 deletions(-) create mode 100644 tests/translator/input/function_with_signing_profile.yaml create mode 100644 tests/translator/output/aws-cn/function_with_signing_profile.json create mode 100644 tests/translator/output/aws-us-gov/function_with_signing_profile.json create mode 100644 tests/translator/output/function_with_signing_profile.json diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index 0cb75661ce..b91f00e75b 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -23,6 +23,7 @@ class LambdaFunction(Resource): "Layers": PropertyType(False, list_of(one_of(is_str(), is_type(dict)))), "ReservedConcurrentExecutions": PropertyType(False, any_type()), "FileSystemConfigs": PropertyType(False, list_of(is_type(dict))), + "CodeSigningConfigArn": PropertyType(False, is_str()), } runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")} diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 007a3ded1d..c6cf507609 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -82,6 +82,7 @@ class SamFunction(SamResourceMacro): "VersionDescription": PropertyType(False, is_str()), "ProvisionedConcurrencyConfig": PropertyType(False, is_type(dict)), "FileSystemConfigs": PropertyType(False, list_of(is_type(dict))), + "CodeSigningConfigArn": PropertyType(False, is_str()), } event_resolver = ResourceTypeResolver( samtranslator.model.eventsources, @@ -412,6 +413,8 @@ def _construct_lambda_function(self): if self.DeadLetterQueue: lambda_function.DeadLetterConfig = {"TargetArn": self.DeadLetterQueue["TargetArn"]} + lambda_function.CodeSigningConfigArn = self.CodeSigningConfigArn + return lambda_function def _add_event_invoke_managed_policy(self, dest_config, logical_id, condition, dest_arn): diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index cf5fb7fb07..f75beb3ec0 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -41,6 +41,7 @@ class Globals(object): "AssumeRolePolicyDocument", "EventInvokeConfig", "FileSystemConfigs", + "CodeSigningConfigArn", ], # Everything except # DefinitionBody: because its hard to reason about merge of Swagger dictionaries diff --git a/tests/translator/input/function_with_signing_profile.yaml b/tests/translator/input/function_with_signing_profile.yaml new file mode 100644 index 0000000000..09a0323670 --- /dev/null +++ b/tests/translator/input/function_with_signing_profile.yaml @@ -0,0 +1,24 @@ +Resources: + + FunctionWithSigningProfile: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/member_portal.zip + Handler: index.gethtml + Runtime: nodejs12.x + CodeSigningConfigArn: !Ref MySignedFunctionCodeSigningConfig + + MySignedFunctionCodeSigningConfig: + Type: AWS::Lambda::CodeSigningConfig + Properties: + Description: "Code Signing for MySignedLambdaFunction" + AllowedPublishers: + SigningProfileVersionArns: + - !GetAtt SigningProfile.ProfileVersionArn + CodeSigningPolicies: + UntrustedArtifactOnDeployment: "Enforce" + + SigningProfile: + Type: AWS::Signer::SigningProfile + Properties: + PlatformId: AWSLambda-SHA384-ECDSA \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_signing_profile.json b/tests/translator/output/aws-cn/function_with_signing_profile.json new file mode 100644 index 0000000000..d25c7e5266 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_signing_profile.json @@ -0,0 +1,85 @@ +{ + "Resources": { + "FunctionWithSigningProfile": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "Role": { + "Fn::GetAtt": [ + "FunctionWithSigningProfileRole", + "Arn" + ] + }, + "CodeSigningConfigArn": { + "Ref": "MySignedFunctionCodeSigningConfig" + } + } + }, + "FunctionWithSigningProfileRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MySignedFunctionCodeSigningConfig": { + "Type": "AWS::Lambda::CodeSigningConfig", + "Properties": { + "CodeSigningPolicies": { + "UntrustedArtifactOnDeployment": "Enforce" + }, + "AllowedPublishers": { + "SigningProfileVersionArns": [ + { + "Fn::GetAtt": [ + "SigningProfile", + "ProfileVersionArn" + ] + } + ] + }, + "Description": "Code Signing for MySignedLambdaFunction" + } + }, + "SigningProfile": { + "Type": "AWS::Signer::SigningProfile", + "Properties": { + "PlatformId": "AWSLambda-SHA384-ECDSA" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_signing_profile.json b/tests/translator/output/aws-us-gov/function_with_signing_profile.json new file mode 100644 index 0000000000..fb4065fa59 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_signing_profile.json @@ -0,0 +1,85 @@ +{ + "Resources": { + "FunctionWithSigningProfile": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "Role": { + "Fn::GetAtt": [ + "FunctionWithSigningProfileRole", + "Arn" + ] + }, + "CodeSigningConfigArn": { + "Ref": "MySignedFunctionCodeSigningConfig" + } + } + }, + "FunctionWithSigningProfileRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MySignedFunctionCodeSigningConfig": { + "Type": "AWS::Lambda::CodeSigningConfig", + "Properties": { + "CodeSigningPolicies": { + "UntrustedArtifactOnDeployment": "Enforce" + }, + "AllowedPublishers": { + "SigningProfileVersionArns": [ + { + "Fn::GetAtt": [ + "SigningProfile", + "ProfileVersionArn" + ] + } + ] + }, + "Description": "Code Signing for MySignedLambdaFunction" + } + }, + "SigningProfile": { + "Type": "AWS::Signer::SigningProfile", + "Properties": { + "PlatformId": "AWSLambda-SHA384-ECDSA" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_globals_unsupported_property.json b/tests/translator/output/error_globals_unsupported_property.json index a97faaf6a2..2b2dfbf18c 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', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'EventInvokeConfig', 'FileSystemConfigs']" + "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', 'ProvisionedConcurrencyConfig', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn']" } ], - "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', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs']" + "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', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn']" } diff --git a/tests/translator/output/function_with_signing_profile.json b/tests/translator/output/function_with_signing_profile.json new file mode 100644 index 0000000000..1b878970d3 --- /dev/null +++ b/tests/translator/output/function_with_signing_profile.json @@ -0,0 +1,85 @@ +{ + "Resources": { + "FunctionWithSigningProfile": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.gethtml", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "member_portal.zip" + }, + "Runtime": "nodejs12.x", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ], + "Role": { + "Fn::GetAtt": [ + "FunctionWithSigningProfileRole", + "Arn" + ] + }, + "CodeSigningConfigArn": { + "Ref": "MySignedFunctionCodeSigningConfig" + } + } + }, + "FunctionWithSigningProfileRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "MySignedFunctionCodeSigningConfig": { + "Type": "AWS::Lambda::CodeSigningConfig", + "Properties": { + "CodeSigningPolicies": { + "UntrustedArtifactOnDeployment": "Enforce" + }, + "AllowedPublishers": { + "SigningProfileVersionArns": [ + { + "Fn::GetAtt": [ + "SigningProfile", + "ProfileVersionArn" + ] + } + ] + }, + "Description": "Code Signing for MySignedLambdaFunction" + } + }, + "SigningProfile": { + "Type": "AWS::Signer::SigningProfile", + "Properties": { + "PlatformId": "AWSLambda-SHA384-ECDSA" + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_function_resources.py b/tests/translator/test_function_resources.py index f5c8c2ab1f..aa2d57f4eb 100644 --- a/tests/translator/test_function_resources.py +++ b/tests/translator/test_function_resources.py @@ -25,6 +25,37 @@ def setUp(self): self.lambda_func = self._make_lambda_function(self.sam_func.logical_id) self.lambda_version = self._make_lambda_version("VersionLogicalId", self.sam_func) + @patch("boto3.session.Session.region_name", "us-west-2") + def test_sam_function_with_code_signer(self): + code_signing_config_arn = "code_signing_config_arn" + func = { + "Type": "AWS::Serverless::Function", + "Properties": { + "CodeUri": self.code_uri, + "Runtime": "nodejs12.x", + "Handler": "index.handler", + "CodeSigningConfigArn": code_signing_config_arn, + }, + } + + sam_func = SamFunction.from_dict(logical_id="foo", resource_dict=func) + + kwargs = {} + kwargs["managed_policy_map"] = {"a": "b"} + kwargs["event_resources"] = [] + kwargs["intrinsics_resolver"] = self.intrinsics_resolver_mock + self.intrinsics_resolver_mock.resolve_parameter_refs.return_value = { + "S3Bucket": "bucket", + "S3Key": "key", + "S3ObjectVersion": "version", + } + resources = sam_func.to_cloudformation(**kwargs) + + lambda_functions = [r.to_dict() for r in resources if r.resource_type == LambdaFunction.resource_type] + self.assertEqual(len(lambda_functions), 1) + expected_code_signing_config_arn = lambda_functions[0]["foo"]["Properties"]["CodeSigningConfigArn"] + self.assertEqual(expected_code_signing_config_arn, code_signing_config_arn) + @patch("boto3.session.Session.region_name", "ap-southeast-1") @patch.object(SamFunction, "_get_resolved_alias_name") def test_sam_function_with_alias(self, get_resolved_alias_name_mock): diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 4db7f1f23d..cc84e40847 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -251,6 +251,7 @@ class TestTranslatorEndToEnd(TestCase): "function_with_conditional_policy_template", "function_with_conditional_policy_template_and_ref_no_value", "function_with_request_parameters", + "function_with_signing_profile", "global_handle_path_level_parameter", "globals_for_function", "globals_for_api", From 6c01c394b9505bc72d642274d1d944c0a4e0edc7 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Wed, 25 Nov 2020 17:40:58 -0800 Subject: [PATCH 05/31] Release v1.31.0 (#1836) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) * Fix merge conflict in release 1.29.0 (#1771) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * Release/v1.29.0 (#1769) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * Release 1.30.0 (#1808) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * fix: Validate API request models (#1757) * Backward merge master branch into develop (#1761) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo * chore: Upgrade outdated dependencies in base.txt and dev.txt (#1744) * chore: Update versions in in base.txt (no effective difference) * chore: Update versions specified with ">=" in dev.txt * Fallback pytest to 4.6.x for python2 * chore: Use Compatible release clause in requirement files (#1762) * Fix: SAM Crashes Invalid swagger models exception (#1765) * Fix: Invalid swagger models exception * Fix: Invalid resource policy exception Co-authored-by: Mufaddal Makati * Fix: DefaultAuth not a string exception (#1774) * Fix: DefaultAuth not a string exception * fix: userpool ref not a string Co-authored-by: Mufaddal Makati * Adding PermissionsBoundary property for State Machine resource (#1772) * Adding PermissionsBoundary property for State Machine resource * Add permissions_boundary to StateMachineGenerator and _construct_role docstrings Co-authored-by: Vaib Suri * fix: use newer policy name in gov & cn regions for xray (#1767) * Update __init__.py (#1775) * Release/v1.29.0 (#1769) (#1773) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * Add Description property to Api and HttApi resources (#1719) * Update DEVELOPMENT_GUIDE; moved from rst to md (#1778) * chore: bump version 1.30.0 (#1792) * Fix conflicts release 1 30 (#1804) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) * Fix merge conflict in release 1.29.0 (#1771) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * Release/v1.29.0 (#1769) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * fix: Validate API request models (#1757) * chore: Upgrade outdated dependencies in base.txt and dev.txt (#1744) * chore: Update versions in in base.txt (no effective difference) * chore: Update versions specified with ">=" in dev.txt * Fallback pytest to 4.6.x for python2 * chore: Use Compatible release clause in requirement files (#1762) * Fix: SAM Crashes Invalid swagger models exception (#1765) * Fix: Invalid swagger models exception * Fix: Invalid resource policy exception Co-authored-by: Mufaddal Makati * Fix: DefaultAuth not a string exception (#1774) * Fix: DefaultAuth not a string exception * fix: userpool ref not a string Co-authored-by: Mufaddal Makati * Adding PermissionsBoundary property for State Machine resource (#1772) * Adding PermissionsBoundary property for State Machine resource * Add permissions_boundary to StateMachineGenerator and _construct_role docstrings Co-authored-by: Vaib Suri * fix: use newer policy name in gov & cn regions for xray (#1767) * Add Description property to Api and HttApi resources (#1719) * Update DEVELOPMENT_GUIDE; moved from rst to md (#1778) * chore: bump version 1.30.0 (#1792) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Anton Grübel * Move Tests to Appveyor (#1801) * print python version * update path vars * update linux cmd * update linux cmd * update linux cmd * update whitelist in tox * update passenv * update tox whitelisting * update tox whitelisting Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Anton Grübel * Fix merge conflicts 1.30.1 (#1816) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * fix: Validate API request models (#1757) * Backward merge master branch into develop (#1761) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo * chore: Upgrade outdated dependencies in base.txt and dev.txt (#1744) * chore: Update versions in in base.txt (no effective difference) * chore: Update versions specified with ">=" in dev.txt * Fallback pytest to 4.6.x for python2 * chore: Use Compatible release clause in requirement files (#1762) * Fix: SAM Crashes Invalid swagger models exception (#1765) * Fix: Invalid swagger models exception * Fix: Invalid resource policy exception Co-authored-by: Mufaddal Makati * Fix: DefaultAuth not a string exception (#1774) * Fix: DefaultAuth not a string exception * fix: userpool ref not a string Co-authored-by: Mufaddal Makati * Adding PermissionsBoundary property for State Machine resource (#1772) * Adding PermissionsBoundary property for State Machine resource * Add permissions_boundary to StateMachineGenerator and _construct_role docstrings Co-authored-by: Vaib Suri * fix: use newer policy name in gov & cn regions for xray (#1767) * Update __init__.py (#1775) * Release/v1.29.0 (#1769) (#1773) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * Add Description property to Api and HttApi resources (#1719) * Update DEVELOPMENT_GUIDE; moved from rst to md (#1778) * chore: bump version 1.30.0 (#1792) * Fix conflicts release 1 30 (#1804) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ * Release/v1.28.0 (#1754) (#1756) * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * bump sam-translator version to v1.28.0 Co-authored-by: Tolledo Co-authored-by: Tolledo * bump version to 1.28.1 (#1758) (#1759) * Fix merge conflict in release 1.29.0 (#1771) * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 * Merge master back to develop (#1734) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Update __init__.py (#1704) * Release/v1.27.0 resolveconflict (#1717) * Release v1.26.0 (#1680) * feat: add support for VPCEndpointIds in EndpointConfiguration * fix: update formatting with black * docs: update 2016-10-31.md * docs: added api endpointconfiguration example * docs: make example more generic * fix: remove nested EndpointConfiguration types from output * fix: only allow one EndpointConfiguration Type * doc: update example to reflect only allowing one EndpointConfiguration Type * fix : missing UserPool properties (#1506) (#1581) * fix: resource policy generation for {path+} (#1580) * refactor: Remove 2016-10-31 examples * update PR template * adjust pr template * Adding authorization scopes as list validation in ApiGatewayAuthorizer (v1 and v2). (#1670) * Adding authorization scopes as list validation in ApiGatewayAuthorizer and ApiGatewayV2Authorizer. * make black. * Adding functional test for invalid auth scope. * adding error condition for invalid test. * removing test template file. * feat: MSK event type support for AWS::Serverless::Function (#52) Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun * Fix: Updated Slack Invite Link (#1712) * Updated Slack Invite Link * Restricted jsonschema to Python 2 * Forced pyrsistent to 0.16 in Python 2 * Reverted Changes to enum34 Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Cosh_ Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Cosh_ * Lambdaauth (#1733) * Add support for Lambda Authorizers in HttpAPI * Address comments and fix formatting * fix version * Validate input parameters. Update tests * black reformat Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> * Feature toggle (#1737) * Adding logic to pipe app config providers. Unit test pending * Adding some documentation to config providers. * Adding some unit tests and making black ignore json files. * minor cleanup. * Addressing PR comments. * feature: Support MTLS auth properties in REST and HTTP API domain names (#1725) * feature: Support MTLS auth properties in REST and HTTP API domain names * fix unicode != str issue in py2.7 * add SecurityPolicy because default RESTAPI is using TLS1.0 * Add new property DisableExecuteApiEndpoint * black reformat * Address comments * Add tests on invalid templates * address test failures in py2.7 * restart travis tests * fix: adding support for passing target id to EventBridgeRule (#1747) * Manage black version using requirement file (#1748) * chore: Manage black version in dev.txt - config pre-commit - config development guide - config travis Refer to the commit below in aws-sam-cli https://github.com/aws/aws-sam-cli/commit/d725db5fbfc698a9f0c7582 * Format using black 20.8b1 * Opt-out black fron dev.txt for Python 2 * Release/v1.29.0 (#1769) * Mq event source (#60) * Support AmazonMQ as event source * Set black hook language version to python3 * chore: version bump (#64) * Black reformat Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> * fix: Validate API request models (#1757) * chore: Upgrade outdated dependencies in base.txt and dev.txt (#1744) * chore: Update versions in in base.txt (no effective difference) * chore: Update versions specified with ">=" in dev.txt * Fallback pytest to 4.6.x for python2 * chore: Use Compatible release clause in requirement files (#1762) * Fix: SAM Crashes Invalid swagger models exception (#1765) * Fix: Invalid swagger models exception * Fix: Invalid resource policy exception Co-authored-by: Mufaddal Makati * Fix: DefaultAuth not a string exception (#1774) * Fix: DefaultAuth not a string exception * fix: userpool ref not a string Co-authored-by: Mufaddal Makati * Adding PermissionsBoundary property for State Machine resource (#1772) * Adding PermissionsBoundary property for State Machine resource * Add permissions_boundary to StateMachineGenerator and _construct_role docstrings Co-authored-by: Vaib Suri * fix: use newer policy name in gov & cn regions for xray (#1767) * Add Description property to Api and HttApi resources (#1719) * Update DEVELOPMENT_GUIDE; moved from rst to md (#1778) * chore: bump version 1.30.0 (#1792) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Anton Grübel * Revert boto3 dependency pin to ~=1.5 (#1810) (#1812) * Revert boto3 dependency pin to ~=1.5 (#1810) * Move Tests to Appveyor (#1801) * print python version * update path vars * update linux cmd * update linux cmd * update linux cmd * update whitelist in tox * update passenv * update tox whitelisting * update tox whitelisting Co-authored-by: javulticat <31746850+javulticat@users.noreply.github.com> Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update __init__.py (#1813) Co-authored-by: Cosh_ Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Tolledo Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Anton Grübel Co-authored-by: Qingchuan Ma <69653965+qingchm@users.noreply.github.com> Co-authored-by: javulticat <31746850+javulticat@users.noreply.github.com> * feature: Support for Lambda Code Signing (#53) (#1825) * feature: add support for CFN fields for lambda signing (#53) * feature: add support for CFN fields for lambda signing * feature: add support for CFN fields for lambda signing (update formatting) * feature: add support for CFN fields for lambda signing (update patching) * feature: add support for CFN fields for lambda signing (update template) * Revert "feat: add explicit UpdateReplacePolicy (#1481)" (#1568) * docs: document IpV6 option on Domain Configuration object (#1588) * chore: Exclude test modules in whl (#1597) * feat: Add Step Function Resource (#1601) Co-authored-by: Jacob Fuss * Release Changes for 1.25.0 * feature: add support for CFN fields for lambda signing * feature: add support for CFN fields for lambda signing (slight code update) * feature: add support for CFN fields for lambda signing (update globals.py) Co-authored-by: Shreya Co-authored-by: Timo Schilling Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood * Move Tests to Appveyor (#1801) * print python version * update path vars * update linux cmd * update linux cmd * update linux cmd * update whitelist in tox * update passenv * update tox whitelisting * update tox whitelisting Co-authored-by: Shreya Co-authored-by: Timo Schilling Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood * merge with master Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Steve Brown Co-authored-by: jtaylor00 Co-authored-by: Jacob Fuss Co-authored-by: Alex Wood Co-authored-by: Tarun Co-authored-by: Raymond Wang <14915548+wchengru@users.noreply.github.com> Co-authored-by: Cosh_ Co-authored-by: Tolledo Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: _sam <3804518+aahung@users.noreply.github.com> Co-authored-by: Kaidi He <73141777+kaidih@users.noreply.github.com> Co-authored-by: Mufaddal Makati Co-authored-by: Mufaddal Makati Co-authored-by: Adam Wong <55506708+wong-a@users.noreply.github.com> Co-authored-by: Vaib Suri Co-authored-by: Anton Grübel Co-authored-by: Qingchuan Ma <69653965+qingchm@users.noreply.github.com> Co-authored-by: javulticat <31746850+javulticat@users.noreply.github.com> Co-authored-by: Shreya Co-authored-by: Timo Schilling Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> --- samtranslator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 4963e4383f..ac4c64769c 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.30.1" +__version__ = "1.31.0" From 270f7c4d26e9d0ee1b46a16951f7154fb0caa19c Mon Sep 17 00:00:00 2001 From: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Date: Tue, 1 Dec 2020 08:33:35 -0800 Subject: [PATCH 06/31] feat(images): additions to SAM spec (#1844) New properties in AWS::Serverless::Function ImageUri -> Code: ImageUri -> AWS::Lambda::Function ImageConfig -> Passthrough -> AWS::Lambda::Function PackageType -> Passthrough -> AWS::Lambda::Function --- samtranslator/__init__.py | 2 +- samtranslator/model/lambda_.py | 4 +- samtranslator/model/packagetype.py | 2 + samtranslator/model/s3_utils/uri_parser.py | 18 + samtranslator/model/sam_resources.py | 112 +++++- tests/model/test_sam_resources.py | 64 +++- .../input/basic_function_withimageuri.yaml | 101 ++++++ .../input/error_function_no_imageuri.yaml | 5 + tests/translator/input/inline_precedence.yaml | 10 + .../aws-cn/basic_function_withimageuri.json | 343 ++++++++++++++++++ .../output/aws-cn/inline_precedence.json | 56 +++ .../basic_function_withimageuri.json | 343 ++++++++++++++++++ .../output/aws-us-gov/inline_precedence.json | 56 +++ .../output/basic_function_withimageuri.json | 343 ++++++++++++++++++ .../output/error_function_no_codeuri.json | 2 +- .../output/error_function_no_handler.json | 6 +- .../output/error_function_no_imageuri.json | 8 + .../output/error_function_no_runtime.json | 4 +- .../translator/output/inline_precedence.json | 56 +++ tests/translator/test_translator.py | 2 + 20 files changed, 1518 insertions(+), 19 deletions(-) create mode 100644 samtranslator/model/packagetype.py create mode 100644 tests/translator/input/basic_function_withimageuri.yaml create mode 100644 tests/translator/input/error_function_no_imageuri.yaml create mode 100644 tests/translator/input/inline_precedence.yaml create mode 100644 tests/translator/output/aws-cn/basic_function_withimageuri.json create mode 100644 tests/translator/output/aws-cn/inline_precedence.json create mode 100644 tests/translator/output/aws-us-gov/basic_function_withimageuri.json create mode 100644 tests/translator/output/aws-us-gov/inline_precedence.json create mode 100644 tests/translator/output/basic_function_withimageuri.json create mode 100644 tests/translator/output/error_function_no_imageuri.json create mode 100644 tests/translator/output/inline_precedence.json diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index ac4c64769c..8b5ae55d95 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.31.0" +__version__ = "1.32.0" diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index b91f00e75b..797842fb37 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -7,10 +7,11 @@ class LambdaFunction(Resource): resource_type = "AWS::Lambda::Function" property_types = { "Code": PropertyType(True, is_type(dict)), + "PackageType": PropertyType(False, is_str()), "DeadLetterConfig": PropertyType(False, is_type(dict)), "Description": PropertyType(False, is_str()), "FunctionName": PropertyType(False, is_str()), - "Handler": PropertyType(True, is_str()), + "Handler": PropertyType(False, is_str()), "MemorySize": PropertyType(False, is_type(int)), "Role": PropertyType(False, is_str()), "Runtime": PropertyType(False, is_str()), @@ -24,6 +25,7 @@ class LambdaFunction(Resource): "ReservedConcurrentExecutions": PropertyType(False, any_type()), "FileSystemConfigs": PropertyType(False, list_of(is_type(dict))), "CodeSigningConfigArn": PropertyType(False, is_str()), + "ImageConfig": PropertyType(False, is_type(dict)), } runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")} diff --git a/samtranslator/model/packagetype.py b/samtranslator/model/packagetype.py new file mode 100644 index 0000000000..429ece4071 --- /dev/null +++ b/samtranslator/model/packagetype.py @@ -0,0 +1,2 @@ +IMAGE = "Image" +ZIP = "Zip" diff --git a/samtranslator/model/s3_utils/uri_parser.py b/samtranslator/model/s3_utils/uri_parser.py index a5188e21c0..f94bec2311 100644 --- a/samtranslator/model/s3_utils/uri_parser.py +++ b/samtranslator/model/s3_utils/uri_parser.py @@ -45,6 +45,24 @@ def to_s3_uri(code_dict): return uri +def construct_image_code_object(image_uri, logical_id, property_name): + """Constructs a Lambda `Code` or `Content` property, from the SAM `ImageUri` property. + This follows the current scheme for Lambda Functions. + + :param string image_uri: string + :param string logical_id: logical_id of the resource calling this function + :param string property_name: name of the property which is used as an input to this function. + :returns: a Code dict, containing the ImageUri. + :rtype: dict + """ + if not image_uri: + raise InvalidResourceException( + logical_id, "'{}' requires that a image hosted at a registry be specified.".format(property_name) + ) + + return {"ImageUri": image_uri} + + def construct_s3_location_object(location_uri, logical_id, property_name): """Constructs a Lambda `Code` or `Content` property, from the SAM `CodeUri` or `ContentUri` property. This follows the current scheme for Lambda Functions and LayerVersions. diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index c6cf507609..bac7d79ce9 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -7,7 +7,8 @@ import samtranslator.model.eventsources.cloudwatchlogs from .api.api_generator import ApiGenerator from .api.http_api_generator import HttpApiGenerator -from .s3_utils.uri_parser import construct_s3_location_object +from .packagetype import ZIP, IMAGE +from .s3_utils.uri_parser import construct_s3_location_object, construct_image_code_object from .tags.resource_tagging import get_tag_list from samtranslator.model import PropertyType, SamResourceMacro, ResourceTypeResolver from samtranslator.model.apigateway import ( @@ -54,9 +55,11 @@ class SamFunction(SamResourceMacro): resource_type = "AWS::Serverless::Function" property_types = { "FunctionName": PropertyType(False, one_of(is_str(), is_type(dict))), - "Handler": PropertyType(True, is_str()), - "Runtime": PropertyType(True, is_str()), + "Handler": PropertyType(False, is_str()), + "Runtime": PropertyType(False, is_str()), "CodeUri": PropertyType(False, one_of(is_str(), is_type(dict))), + "ImageUri": PropertyType(False, is_str()), + "PackageType": PropertyType(False, is_str()), "InlineCode": PropertyType(False, one_of(is_str(), is_type(dict))), "DeadLetterQueue": PropertyType(False, is_type(dict)), "Description": PropertyType(False, is_str()), @@ -82,6 +85,7 @@ class SamFunction(SamResourceMacro): "VersionDescription": PropertyType(False, is_str()), "ProvisionedConcurrencyConfig": PropertyType(False, is_type(dict)), "FileSystemConfigs": PropertyType(False, list_of(is_type(dict))), + "ImageConfig": PropertyType(False, is_type(dict)), "CodeSigningConfigArn": PropertyType(False, is_str()), } event_resolver = ResourceTypeResolver( @@ -406,6 +410,8 @@ def _construct_lambda_function(self): lambda_function.Tags = self._construct_tag_list(self.Tags) lambda_function.Layers = self.Layers lambda_function.FileSystemConfigs = self.FileSystemConfigs + lambda_function.ImageConfig = self.ImageConfig + lambda_function.PackageType = self.PackageType if self.Tracing: lambda_function.TracingConfig = {"Mode": self.Tracing} @@ -415,6 +421,7 @@ def _construct_lambda_function(self): lambda_function.CodeSigningConfigArn = self.CodeSigningConfigArn + self._validate_package_type(lambda_function) return lambda_function def _add_event_invoke_managed_policy(self, dest_config, logical_id, condition, dest_arn): @@ -491,6 +498,50 @@ def _construct_role(self, managed_policy_map, event_invoke_policies): ) return execution_role + def _validate_package_type(self, lambda_function): + """ + Validates Function based on the existence of Package type + """ + packagetype = lambda_function.PackageType or ZIP + + if packagetype not in [ZIP, IMAGE]: + raise InvalidResourceException( + lambda_function.logical_id, + "PackageType needs to be `{zip}` or `{image}`".format(zip=ZIP, image=IMAGE), + ) + + def _validate_package_type_zip(): + if not all([lambda_function.Runtime, lambda_function.Handler]): + raise InvalidResourceException( + lambda_function.logical_id, + "Runtime and Handler needs to be present when PackageType is of type `{zip}`".format(zip=ZIP), + ) + + if any([lambda_function.Code.get("ImageUri", False), lambda_function.ImageConfig]): + raise InvalidResourceException( + lambda_function.logical_id, + "ImageUri or ImageConfig cannot be present when PackageType is of type `{zip}`".format(zip=ZIP), + ) + + def _validate_package_type_image(): + if any([lambda_function.Handler, lambda_function.Runtime, lambda_function.Layers]): + raise InvalidResourceException( + lambda_function.logical_id, + "Runtime, Handler, Layers cannot be present when PackageType is of type `{image}`".format( + image=IMAGE + ), + ) + if not lambda_function.Code.get("ImageUri"): + raise InvalidResourceException( + lambda_function.logical_id, + "ImageUri needs to be present when PackageType is of type `{image}`".format(image=IMAGE), + ) + + _validate_per_package_type = {ZIP: _validate_package_type_zip, IMAGE: _validate_package_type_image} + + # Call appropriate validation function based on the package type. + return _validate_per_package_type[packagetype]() + def _validate_dlq(self): """Validates whether the DeadLetterQueue LogicalId is validation :raise: InvalidResourceException @@ -577,12 +628,59 @@ def _generate_event_resources( return resources def _construct_code_dict(self): - if self.InlineCode: + """Constructs Lambda Code Dictionary based on the accepted SAM artifact properties such + as `InlineCode`, `CodeUri` and `ImageUri` and also raises errors if more than one of them is + defined. `PackageType` determines which artifacts are considered. + + :raises InvalidResourceException when conditions on the SAM artifact properties are not met. + """ + # list of accepted artifacts + packagetype = self.PackageType or ZIP + artifacts = {} + + if packagetype == ZIP: + artifacts = {"InlineCode": self.InlineCode, "CodeUri": self.CodeUri} + elif packagetype == IMAGE: + artifacts = {"ImageUri": self.ImageUri} + + if packagetype not in [ZIP, IMAGE]: + raise InvalidResourceException(self.logical_id, "invalid 'PackageType' : {}".format(packagetype)) + + # Inline function for transformation of inline code. + # It accepts arbitrary argumemnts, because the arguments do not matter for the result. + def _construct_inline_code(*args, **kwargs): return {"ZipFile": self.InlineCode} - elif self.CodeUri: - return construct_s3_location_object(self.CodeUri, self.logical_id, "CodeUri") + + # dispatch mechanism per artifact on how it needs to be transformed. + artifact_dispatch = { + "InlineCode": _construct_inline_code, + "CodeUri": construct_s3_location_object, + "ImageUri": construct_image_code_object, + } + + filtered_artifacts = dict(filter(lambda x: x[1] != None, artifacts.items())) + # There are more than one allowed artifact types present, raise an Error. + # There are no valid artifact types present, also raise an Error. + if len(filtered_artifacts) > 1 or len(filtered_artifacts) == 0: + if packagetype == ZIP and len(filtered_artifacts) == 0: + raise InvalidResourceException(self.logical_id, "Only one of 'InlineCode' or 'CodeUri' can be set.") + elif packagetype == IMAGE: + raise InvalidResourceException(self.logical_id, "'ImageUri' must be set.") + + filtered_keys = [key for key in filtered_artifacts.keys()] + # NOTE(sriram-mv): This precedence order is important. It is protect against python2 vs python3 + # dictionary ordering when getting the key values with .keys() on a dictionary. + # Do not change this precedence order. + if "InlineCode" in filtered_keys: + filtered_key = "InlineCode" + elif "CodeUri" in filtered_keys: + filtered_key = "CodeUri" + elif "ImageUri" in filtered_keys: + filtered_key = "ImageUri" else: - raise InvalidResourceException(self.logical_id, "Either 'InlineCode' or 'CodeUri' must be set") + raise InvalidResourceException(self.logical_id, "Either 'InlineCode' or 'CodeUri' must be set.") + dispatch_function = artifact_dispatch[filtered_key] + return dispatch_function(artifacts[filtered_key], self.logical_id, filtered_key) def _construct_version(self, function, intrinsics_resolver, code_sha256=None): """Constructs a Lambda Version resource that will be auto-published when CodeUri of the function changes. diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index 8d1fa83fff..bd6a6789d4 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -6,14 +6,14 @@ from samtranslator.model import InvalidResourceException from samtranslator.model.apigatewayv2 import ApiGatewayV2HttpApi from samtranslator.model.lambda_ import LambdaFunction, LambdaVersion -from samtranslator.model.apigateway import ApiGatewayRestApi -from samtranslator.model.apigateway import ApiGatewayDeployment +from samtranslator.model.apigateway import ApiGatewayDeployment, ApiGatewayRestApi from samtranslator.model.apigateway import ApiGatewayStage from samtranslator.model.iam import IAMRole +from samtranslator.model.packagetype import IMAGE, ZIP from samtranslator.model.sam_resources import SamFunction, SamApi, SamHttpApi -class TestCodeUri(TestCase): +class TestCodeUriandImageUri(TestCase): kwargs = { "intrinsics_resolver": IntrinsicsResolver({}), "event_resources": [], @@ -24,6 +24,8 @@ class TestCodeUri(TestCase): def test_with_code_uri(self): function = SamFunction("foo") function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "foo" + function.Handler = "bar" cfnResources = function.to_cloudformation(**self.kwargs) generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)] @@ -34,14 +36,62 @@ def test_with_code_uri(self): def test_with_zip_file(self): function = SamFunction("foo") function.InlineCode = "hello world" + function.Runtime = "foo" + function.Handler = "bar" cfnResources = function.to_cloudformation(**self.kwargs) generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)] self.assertEqual(generatedFunctionList.__len__(), 1) self.assertEqual(generatedFunctionList[0].Code, {"ZipFile": "hello world"}) - def test_with_no_code_uri_or_zipfile(self): + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_no_code_uri_or_zipfile_or_no_image_uri(self): + function = SamFunction("foo") + with pytest.raises(InvalidResourceException): + function.to_cloudformation(**self.kwargs) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_image_uri(self): + function = SamFunction("foo") + function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest" + function.PackageType = IMAGE + cfnResources = function.to_cloudformation(**self.kwargs) + generatedFunctionList = [x for x in cfnResources if isinstance(x, LambdaFunction)] + self.assertEqual(generatedFunctionList.__len__(), 1) + self.assertEqual(generatedFunctionList[0].Code, {"ImageUri": function.ImageUri}) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_image_uri_layers_runtime_handler(self): + function = SamFunction("foo") + function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest" + function.Layers = ["Layer1"] + function.Runtime = "foo" + function.Handler = "bar" + function.PackageType = IMAGE + with pytest.raises(InvalidResourceException): + function.to_cloudformation(**self.kwargs) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_image_uri_package_type_zip(self): + function = SamFunction("foo") + function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest" + function.PackageType = ZIP + with pytest.raises(InvalidResourceException): + function.to_cloudformation(**self.kwargs) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_image_uri_invalid_package_type(self): function = SamFunction("foo") + function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest" + function.PackageType = "fake" + with pytest.raises(InvalidResourceException): + function.to_cloudformation(**self.kwargs) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_with_image_uri_and_code_uri(self): + function = SamFunction("foo") + function.ImageUri = "123456789.dkr.ecr.us-east-1.amazonaws.com/myimage:latest" + function.CodeUri = "s3://foobar/foo.zip" with pytest.raises(InvalidResourceException): function.to_cloudformation(**self.kwargs) @@ -57,6 +107,8 @@ class TestAssumeRolePolicyDocument(TestCase): def test_with_assume_role_policy_document(self): function = SamFunction("foo") function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "foo" + function.Handler = "bar" assume_role_policy_document = { "Version": "2012-10-17", @@ -79,6 +131,8 @@ def test_with_assume_role_policy_document(self): def test_without_assume_role_policy_document(self): function = SamFunction("foo") function.CodeUri = "s3://foobar/foo.zip" + function.Runtime = "foo" + function.Handler = "bar" assume_role_policy_document = { "Version": "2012-10-17", @@ -104,6 +158,8 @@ def test_with_version_description(self): function = SamFunction("foo") test_description = "foobar" + function.Runtime = "foo" + function.Handler = "bar" function.CodeUri = "s3://foobar/foo.zip" function.VersionDescription = test_description function.AutoPublishAlias = "live" diff --git a/tests/translator/input/basic_function_withimageuri.yaml b/tests/translator/input/basic_function_withimageuri.yaml new file mode 100644 index 0000000000..897642b555 --- /dev/null +++ b/tests/translator/input/basic_function_withimageuri.yaml @@ -0,0 +1,101 @@ +Parameters: + SomeParameter: + Type: String + Default: param + SomeOtherParameter: + Type: String + Default: otherparam +Resources: + MinimalImageFunction: + Type: 'AWS::Serverless::Function' + Properties: + ImageUri: 123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest + PackageType: Image + + MinimalImageFunctionPackageTypeAndImageConfig: + Type: 'AWS::Serverless::Function' + Properties: + ImageUri: 123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest + PackageType: Image + ImageConfig: + WorkingDirectory: /var/task + Command: /bin/sh + EntryPoint: "echo hello world!" + + CompleteImageFunction: + Type: 'AWS::Serverless::Function' + Properties: + FunctionName: MyAwesomeFunction + ImageUri: 123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest + PackageType: Image + Description: Starter Lambda Function + Timeout: 60 + VpcConfig: + SecurityGroupIds: + - sg-edcd9784 + SubnetIds: + - subnet-9d4a7b6c + - subnet-65ea5f08 + - {Ref: SomeParameter} + - {Ref: SomeOtherParameter} + Role: arn:aws:iam::012345678901:role/lambda_basic_execution + ImageConfig: + WorkingDirectory: /var/task + Command: /bin/sh + EntryPoint: "echo hello world!" + Environment: + Variables: + Name: Value + Name2: Value2 + + FunctionWithPolicies: + Type: 'AWS::Serverless::Function' + Properties: + ImageUri: 123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest + PackageType: Image + Policies: AmazonDynamoDBFullAccess + + FunctionWithPolicyDocument: + Type: 'AWS::Serverless::Function' + Properties: + ImageUri: 123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest + PackageType: Image + Policies: + Statement: + - Action: [ 'dynamodb:*' ] + Effect: Allow + Resource: '*' + + FunctionWithRoleRef: + Type: 'AWS::Serverless::Function' + Properties: + ImageUri: 123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest + PackageType: Image + Role: + Fn::GetAtt: ["MyFunctionRole", "Arn"] + + MyFunctionRole: + # This is just some role. Actual role definition might be wrong + Type: "AWS::IAM::Role" + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Principal: + Service: + - "ec2.amazonaws.com" + Action: + - "sts:AssumeRole" + Path: "/" + Policies: + - + PolicyName: "root" + PolicyDocument: + Version: "2012-10-17" + Statement: + - + Effect: "Allow" + Action: "*" + Resource: "*" diff --git a/tests/translator/input/error_function_no_imageuri.yaml b/tests/translator/input/error_function_no_imageuri.yaml new file mode 100644 index 0000000000..05191e50f5 --- /dev/null +++ b/tests/translator/input/error_function_no_imageuri.yaml @@ -0,0 +1,5 @@ +Resources: + Function: + Type: 'AWS::Serverless::Function' + Properties: + PackageType: Image diff --git a/tests/translator/input/inline_precedence.yaml b/tests/translator/input/inline_precedence.yaml new file mode 100644 index 0000000000..04812fb297 --- /dev/null +++ b/tests/translator/input/inline_precedence.yaml @@ -0,0 +1,10 @@ +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + "def lambda_handler(): + pass" + CodeUri: . + Handler: index.lambda_handler + Runtime: python3.8 \ No newline at end of file diff --git a/tests/translator/output/aws-cn/basic_function_withimageuri.json b/tests/translator/output/aws-cn/basic_function_withimageuri.json new file mode 100644 index 0000000000..894fbb717c --- /dev/null +++ b/tests/translator/output/aws-cn/basic_function_withimageuri.json @@ -0,0 +1,343 @@ +{ + "Parameters": { + "SomeParameter": { + "Type": "String", + "Default": "param" + }, + "SomeOtherParameter": { + "Type": "String", + "Default": "otherparam" + } + }, + "Resources": { + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "ec2.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "Policies": [ + { + "PolicyName": "root", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] + } + } + ] + } + }, + "MinimalImageFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MinimalImageFunctionRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalImageFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalImageFunctionPackageTypeAndImageConfig": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MinimalImageFunctionPackageTypeAndImageConfigRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "ImageConfig": { + "WorkingDirectory": "/var/task", + "Command": "/bin/sh", + "EntryPoint": "echo hello world!" + } + } + }, + "MinimalImageFunctionPackageTypeAndImageConfigRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "CompleteImageFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Description": "Starter Lambda Function", + "FunctionName": "MyAwesomeFunction", + "Role": "arn:aws:iam::012345678901:role/lambda_basic_execution", + "Timeout": 60, + "VpcConfig": { + "SecurityGroupIds": [ + "sg-edcd9784" + ], + "SubnetIds": [ + "subnet-9d4a7b6c", + "subnet-65ea5f08", + { + "Ref": "SomeParameter" + }, + { + "Ref": "SomeOtherParameter" + } + ] + }, + "Environment": { + "Variables": { + "Name": "Value", + "Name2": "Value2" + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "ImageConfig": { + "WorkingDirectory": "/var/task", + "Command": "/bin/sh", + "EntryPoint": "echo hello world!" + } + } + }, + "FunctionWithPolicies": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "FunctionWithPoliciesRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPoliciesRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-cn:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPolicyDocument": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "FunctionWithPolicyDocumentRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPolicyDocumentRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "FunctionWithPolicyDocumentRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithRoleRef": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/inline_precedence.json b/tests/translator/output/aws-cn/inline_precedence.json new file mode 100644 index 0000000000..5a3b8edd7f --- /dev/null +++ b/tests/translator/output/aws-cn/inline_precedence.json @@ -0,0 +1,56 @@ +{ + "Resources": { + "HelloWorldFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HelloWorldFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.lambda_handler", + "Code": { + "ZipFile": "\"def lambda_handler():\n pass\"\n" + }, + "Role": { + "Fn::GetAtt": [ + "HelloWorldFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.8", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/basic_function_withimageuri.json b/tests/translator/output/aws-us-gov/basic_function_withimageuri.json new file mode 100644 index 0000000000..bb91395e60 --- /dev/null +++ b/tests/translator/output/aws-us-gov/basic_function_withimageuri.json @@ -0,0 +1,343 @@ +{ + "Parameters": { + "SomeParameter": { + "Type": "String", + "Default": "param" + }, + "SomeOtherParameter": { + "Type": "String", + "Default": "otherparam" + } + }, + "Resources": { + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "ec2.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "Policies": [ + { + "PolicyName": "root", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] + } + } + ] + } + }, + "MinimalImageFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MinimalImageFunctionRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalImageFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalImageFunctionPackageTypeAndImageConfig": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MinimalImageFunctionPackageTypeAndImageConfigRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "ImageConfig": { + "WorkingDirectory": "/var/task", + "Command": "/bin/sh", + "EntryPoint": "echo hello world!" + } + } + }, + "MinimalImageFunctionPackageTypeAndImageConfigRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "CompleteImageFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Description": "Starter Lambda Function", + "FunctionName": "MyAwesomeFunction", + "Role": "arn:aws:iam::012345678901:role/lambda_basic_execution", + "Timeout": 60, + "VpcConfig": { + "SecurityGroupIds": [ + "sg-edcd9784" + ], + "SubnetIds": [ + "subnet-9d4a7b6c", + "subnet-65ea5f08", + { + "Ref": "SomeParameter" + }, + { + "Ref": "SomeOtherParameter" + } + ] + }, + "Environment": { + "Variables": { + "Name": "Value", + "Name2": "Value2" + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "ImageConfig": { + "WorkingDirectory": "/var/task", + "Command": "/bin/sh", + "EntryPoint": "echo hello world!" + } + } + }, + "FunctionWithPolicies": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "FunctionWithPoliciesRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPoliciesRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws-us-gov:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPolicyDocument": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "FunctionWithPolicyDocumentRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPolicyDocumentRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "FunctionWithPolicyDocumentRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithRoleRef": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/inline_precedence.json b/tests/translator/output/aws-us-gov/inline_precedence.json new file mode 100644 index 0000000000..7d650c33ae --- /dev/null +++ b/tests/translator/output/aws-us-gov/inline_precedence.json @@ -0,0 +1,56 @@ +{ + "Resources": { + "HelloWorldFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HelloWorldFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.lambda_handler", + "Code": { + "ZipFile": "\"def lambda_handler():\n pass\"\n" + }, + "Role": { + "Fn::GetAtt": [ + "HelloWorldFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.8", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/output/basic_function_withimageuri.json b/tests/translator/output/basic_function_withimageuri.json new file mode 100644 index 0000000000..2dfe458983 --- /dev/null +++ b/tests/translator/output/basic_function_withimageuri.json @@ -0,0 +1,343 @@ +{ + "Parameters": { + "SomeParameter": { + "Type": "String", + "Default": "param" + }, + "SomeOtherParameter": { + "Type": "String", + "Default": "otherparam" + } + }, + "Resources": { + "MyFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": [ + "ec2.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ] + } + ] + }, + "Path": "/", + "Policies": [ + { + "PolicyName": "root", + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "*", + "Resource": "*" + } + ] + } + } + ] + } + }, + "MinimalImageFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MinimalImageFunctionRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalImageFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MinimalImageFunctionPackageTypeAndImageConfig": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MinimalImageFunctionPackageTypeAndImageConfigRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "ImageConfig": { + "WorkingDirectory": "/var/task", + "Command": "/bin/sh", + "EntryPoint": "echo hello world!" + } + } + }, + "MinimalImageFunctionPackageTypeAndImageConfigRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "CompleteImageFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Description": "Starter Lambda Function", + "FunctionName": "MyAwesomeFunction", + "Role": "arn:aws:iam::012345678901:role/lambda_basic_execution", + "Timeout": 60, + "VpcConfig": { + "SecurityGroupIds": [ + "sg-edcd9784" + ], + "SubnetIds": [ + "subnet-9d4a7b6c", + "subnet-65ea5f08", + { + "Ref": "SomeParameter" + }, + { + "Ref": "SomeOtherParameter" + } + ] + }, + "Environment": { + "Variables": { + "Name": "Value", + "Name2": "Value2" + } + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ], + "ImageConfig": { + "WorkingDirectory": "/var/task", + "Command": "/bin/sh", + "EntryPoint": "echo hello world!" + } + } + }, + "FunctionWithPolicies": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "FunctionWithPoliciesRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPoliciesRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPolicyDocument": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "FunctionWithPolicyDocumentRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithPolicyDocumentRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "FunctionWithPolicyDocumentRolePolicy0", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "dynamodb:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "FunctionWithRoleRef": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ImageUri": "123456789.dkr.ecr.region.amazonaws.suffix/myimage:latest" + }, + "PackageType": "Image", + "Role": { + "Fn::GetAtt": [ + "MyFunctionRole", + "Arn" + ] + }, + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_function_no_codeuri.json b/tests/translator/output/error_function_no_codeuri.json index 90415a9504..2de25914fe 100644 --- a/tests/translator/output/error_function_no_codeuri.json +++ b/tests/translator/output/error_function_no_codeuri.json @@ -4,5 +4,5 @@ "errorMessage": "Resource with id [Function] is invalid. Either 'InlineCode' or 'CodeUri' must be set." } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Either 'InlineCode' or 'CodeUri' must be set" + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Only one of 'InlineCode' or 'CodeUri' can be set." } diff --git a/tests/translator/output/error_function_no_handler.json b/tests/translator/output/error_function_no_handler.json index 46323933fc..8bd371adec 100644 --- a/tests/translator/output/error_function_no_handler.json +++ b/tests/translator/output/error_function_no_handler.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [Function] is invalid. Missing required property 'Handler'." + "errorMessage": "Resource with id [Function] is invalid. Runtime and Handler needs to be present when PackageType is of type `Zip`" } - ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Missing required property 'Handler'." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Runtime and Handler needs to be present when PackageType is of type `Zip`" } \ No newline at end of file diff --git a/tests/translator/output/error_function_no_imageuri.json b/tests/translator/output/error_function_no_imageuri.json new file mode 100644 index 0000000000..b07123c0ab --- /dev/null +++ b/tests/translator/output/error_function_no_imageuri.json @@ -0,0 +1,8 @@ +{ + "errors": [ + { + "errorMessage": "Resource with id [Function] is invalid. 'ImageUri' must be set." + } + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. 'ImageUri' must be set." +} diff --git a/tests/translator/output/error_function_no_runtime.json b/tests/translator/output/error_function_no_runtime.json index 34bae18a53..ed1b9f21bb 100644 --- a/tests/translator/output/error_function_no_runtime.json +++ b/tests/translator/output/error_function_no_runtime.json @@ -1,8 +1,8 @@ { "errors": [ { - "errorMessage": "Resource with id [Function] is invalid. Missing required property 'Runtime'." + "errorMessage": "Resource with id [Function] is invalid. Runtime and Handler needs to be present when PackageType is of type `Zip`" } ], - "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Missing required property 'Runtime'." + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [Function] is invalid. Runtime and Handler needs to be present when PackageType is of type `Zip`" } \ No newline at end of file diff --git a/tests/translator/output/inline_precedence.json b/tests/translator/output/inline_precedence.json new file mode 100644 index 0000000000..6ea4caaf01 --- /dev/null +++ b/tests/translator/output/inline_precedence.json @@ -0,0 +1,56 @@ +{ + "Resources": { + "HelloWorldFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "HelloWorldFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "index.lambda_handler", + "Code": { + "ZipFile": "\"def lambda_handler():\n pass\"\n" + }, + "Role": { + "Fn::GetAtt": [ + "HelloWorldFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.8", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index cc84e40847..7318e566e3 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -146,6 +146,7 @@ class TestTranslatorEndToEnd(TestCase): "s3_with_condition", "function_with_condition", "basic_function", + "basic_function_withimageuri", "basic_application", "application_preparing_state", "application_with_intrinsics", @@ -267,6 +268,7 @@ class TestTranslatorEndToEnd(TestCase): "implicit_api_with_auth_and_conditions_max", "implicit_api_with_many_conditions", "implicit_and_explicit_api_with_conditions", + "inline_precedence", "api_with_cors_and_conditions_no_definitionbody", "api_with_auth_and_conditions_all_max", "api_with_apikey_default_override", From febde6e9b07060f41e8b146baa45a8483c662037 Mon Sep 17 00:00:00 2001 From: ejafarli <54083696+ejafarli@users.noreply.github.com> Date: Wed, 2 Dec 2020 12:17:53 -0800 Subject: [PATCH 07/31] Support DLQ, RetryPolicy properties for EventBridgeRule,Schedule event sources (#1842) * Add DeadLetterConfig,RetryPolicy properties for EventBridgeRule,Schedule event sources * Minor fix,rename function argument * Update test class name * Combine dlq extraction/generation into the utility class * Remove unused import --- docs/cloudformation_compatibility.rst | 4 + docs/internals/generated_resources.rst | 11 + samtranslator/model/eventbridge_utils.py | 54 ++++ samtranslator/model/eventsources/push.py | 43 +++- samtranslator/model/sqs.py | 20 +- samtranslator/model/stepfunctions/events.py | 41 ++- .../validator/sam_schema/schema.json | 28 +++ .../test_eventbridge_rule_source.py | 78 ++++++ .../test_schedule_event_source.py | 66 +++++ .../test_cloudwatchevents_event.py | 53 ++++ .../stepfunctions/test_schedule_event.py | 53 ++++ ...n_with_cwe_both_dlq_property_provided.yaml | 19 ++ ...or_function_with_cwe_invalid_dlq_type.yaml | 18 ++ ...unction_with_cwe_missing_dlq_property.yaml | 18 ++ ...h_schedule_both_dlq_property_provided.yaml | 15 ++ ...nction_with_schedule_invalid_dlq_type.yaml | 14 ++ ...on_with_schedule_missing_dlq_property.yaml | 14 ++ ...e_with_cwe_both_dlq_property_provided.yaml | 17 ++ ...ate_machine_with_cwe_invalid_dlq_type.yaml | 16 ++ ...machine_with_cwe_missing_dlq_property.yaml | 16 ++ ...h_schedule_both_dlq_property_provided.yaml | 17 ++ ...achine_with_schedule_invalid_dlq_type.yaml | 17 ++ ...ne_with_schedule_missing_dlq_property.yaml | 17 ++ .../input/eventbridgerule_with_dlq.yaml | 31 +++ .../eventbridgerule_with_retry_policy.yaml | 32 +++ .../input/state_machine_with_eb_dlq.yaml | 16 ++ .../state_machine_with_eb_dlq_generated.yaml | 17 ++ .../state_machine_with_eb_retry_policy.yaml | 17 ++ ...achine_with_schedule_dlq_retry_policy.yaml | 19 ++ .../aws-cn/eventbridgerule_with_dlq.json | 238 ++++++++++++++++++ .../eventbridgerule_with_retry_policy.json | 189 ++++++++++++++ .../aws-cn/state_machine_with_eb_dlq.json | 86 +++++++ .../state_machine_with_eb_dlq_generated.json | 133 ++++++++++ .../state_machine_with_eb_retry_policy.json | 87 +++++++ ...achine_with_schedule_dlq_retry_policy.json | 87 +++++++ .../aws-us-gov/eventbridgerule_with_dlq.json | 238 ++++++++++++++++++ .../eventbridgerule_with_retry_policy.json | 189 ++++++++++++++ .../aws-us-gov/state_machine_with_eb_dlq.json | 86 +++++++ .../state_machine_with_eb_dlq_generated.json | 133 ++++++++++ .../state_machine_with_eb_retry_policy.json | 87 +++++++ ...achine_with_schedule_dlq_retry_policy.json | 87 +++++++ ...n_with_cwe_both_dlq_property_provided.json | 3 + ...or_function_with_cwe_invalid_dlq_type.json | 3 + ...unction_with_cwe_missing_dlq_property.json | 3 + ...h_schedule_both_dlq_property_provided.json | 3 + ...nction_with_schedule_invalid_dlq_type.json | 3 + ...on_with_schedule_missing_dlq_property.json | 3 + ...e_with_cwe_both_dlq_property_provided.json | 3 + ...ate_machine_with_cwe_invalid_dlq_type.json | 3 + ...machine_with_cwe_missing_dlq_property.json | 3 + ...h_schedule_both_dlq_property_provided.json | 3 + ...achine_with_schedule_invalid_dlq_type.json | 3 + ...ne_with_schedule_missing_dlq_property.json | 3 + .../output/eventbridgerule_with_dlq.json | 238 ++++++++++++++++++ .../eventbridgerule_with_retry_policy.json | 189 ++++++++++++++ .../output/state_machine_with_eb_dlq.json | 86 +++++++ .../state_machine_with_eb_dlq_generated.json | 133 ++++++++++ .../state_machine_with_eb_retry_policy.json | 87 +++++++ ...achine_with_schedule_dlq_retry_policy.json | 87 +++++++ tests/translator/test_translator.py | 18 ++ 60 files changed, 3283 insertions(+), 12 deletions(-) create mode 100644 samtranslator/model/eventbridge_utils.py create mode 100644 tests/model/eventsources/test_eventbridge_rule_source.py create mode 100644 tests/model/eventsources/test_schedule_event_source.py create mode 100644 tests/translator/input/error_function_with_cwe_both_dlq_property_provided.yaml create mode 100644 tests/translator/input/error_function_with_cwe_invalid_dlq_type.yaml create mode 100644 tests/translator/input/error_function_with_cwe_missing_dlq_property.yaml create mode 100644 tests/translator/input/error_function_with_schedule_both_dlq_property_provided.yaml create mode 100644 tests/translator/input/error_function_with_schedule_invalid_dlq_type.yaml create mode 100644 tests/translator/input/error_function_with_schedule_missing_dlq_property.yaml create mode 100644 tests/translator/input/error_state_machine_with_cwe_both_dlq_property_provided.yaml create mode 100644 tests/translator/input/error_state_machine_with_cwe_invalid_dlq_type.yaml create mode 100644 tests/translator/input/error_state_machine_with_cwe_missing_dlq_property.yaml create mode 100644 tests/translator/input/error_state_machine_with_schedule_both_dlq_property_provided.yaml create mode 100644 tests/translator/input/error_state_machine_with_schedule_invalid_dlq_type.yaml create mode 100644 tests/translator/input/error_state_machine_with_schedule_missing_dlq_property.yaml create mode 100644 tests/translator/input/eventbridgerule_with_dlq.yaml create mode 100644 tests/translator/input/eventbridgerule_with_retry_policy.yaml create mode 100644 tests/translator/input/state_machine_with_eb_dlq.yaml create mode 100644 tests/translator/input/state_machine_with_eb_dlq_generated.yaml create mode 100644 tests/translator/input/state_machine_with_eb_retry_policy.yaml create mode 100644 tests/translator/input/state_machine_with_schedule_dlq_retry_policy.yaml create mode 100644 tests/translator/output/aws-cn/eventbridgerule_with_dlq.json create mode 100644 tests/translator/output/aws-cn/eventbridgerule_with_retry_policy.json create mode 100644 tests/translator/output/aws-cn/state_machine_with_eb_dlq.json create mode 100644 tests/translator/output/aws-cn/state_machine_with_eb_dlq_generated.json create mode 100644 tests/translator/output/aws-cn/state_machine_with_eb_retry_policy.json create mode 100644 tests/translator/output/aws-cn/state_machine_with_schedule_dlq_retry_policy.json create mode 100644 tests/translator/output/aws-us-gov/eventbridgerule_with_dlq.json create mode 100644 tests/translator/output/aws-us-gov/eventbridgerule_with_retry_policy.json create mode 100644 tests/translator/output/aws-us-gov/state_machine_with_eb_dlq.json create mode 100644 tests/translator/output/aws-us-gov/state_machine_with_eb_dlq_generated.json create mode 100644 tests/translator/output/aws-us-gov/state_machine_with_eb_retry_policy.json create mode 100644 tests/translator/output/aws-us-gov/state_machine_with_schedule_dlq_retry_policy.json create mode 100644 tests/translator/output/error_function_with_cwe_both_dlq_property_provided.json create mode 100644 tests/translator/output/error_function_with_cwe_invalid_dlq_type.json create mode 100644 tests/translator/output/error_function_with_cwe_missing_dlq_property.json create mode 100644 tests/translator/output/error_function_with_schedule_both_dlq_property_provided.json create mode 100644 tests/translator/output/error_function_with_schedule_invalid_dlq_type.json create mode 100644 tests/translator/output/error_function_with_schedule_missing_dlq_property.json create mode 100644 tests/translator/output/error_state_machine_with_cwe_both_dlq_property_provided.json create mode 100644 tests/translator/output/error_state_machine_with_cwe_invalid_dlq_type.json create mode 100644 tests/translator/output/error_state_machine_with_cwe_missing_dlq_property.json create mode 100644 tests/translator/output/error_state_machine_with_schedule_both_dlq_property_provided.json create mode 100644 tests/translator/output/error_state_machine_with_schedule_invalid_dlq_type.json create mode 100644 tests/translator/output/error_state_machine_with_schedule_missing_dlq_property.json create mode 100644 tests/translator/output/eventbridgerule_with_dlq.json create mode 100644 tests/translator/output/eventbridgerule_with_retry_policy.json create mode 100644 tests/translator/output/state_machine_with_eb_dlq.json create mode 100644 tests/translator/output/state_machine_with_eb_dlq_generated.json create mode 100644 tests/translator/output/state_machine_with_eb_retry_policy.json create mode 100644 tests/translator/output/state_machine_with_schedule_dlq_retry_policy.json diff --git a/docs/cloudformation_compatibility.rst b/docs/cloudformation_compatibility.rst index 5cac0920aa..e5d2d34739 100644 --- a/docs/cloudformation_compatibility.rst +++ b/docs/cloudformation_compatibility.rst @@ -169,6 +169,8 @@ CloudWatchEvent (superseded by EventBridgeRule, see below) Pattern All Input All InputPath All +DeadLetterConfig All +RetryPolicy All ======================== ================================== ======================== EventBridgeRule @@ -179,6 +181,8 @@ EventBridgeRule Pattern All Input All InputPath All +DeadLetterConfig All +RetryPolicy All ======================== ================================== ======================== IotRule diff --git a/docs/internals/generated_resources.rst b/docs/internals/generated_resources.rst index 3d03cd1480..8b2760d615 100644 --- a/docs/internals/generated_resources.rst +++ b/docs/internals/generated_resources.rst @@ -455,6 +455,8 @@ Example: Type: Schedule Properties: Input: rate(5 minutes) + DeadLetterConfig: + Type: SQS ... Additional generated resources: @@ -464,6 +466,8 @@ CloudFormation Resource Type Logical ID ================================== ================================ AWS::Lambda::Permission MyFunction\ **MyTimer**\ Permission AWS::Events::Rule MyFunction\ **MyTimer** +AWS::SQS::Queue MyFunction\ **MyTimer**\ Queue +AWS::SQS::QueuePolicy MyFunction\ **MyTimer**\ QueuePolicy ================================== ================================ CloudWatchEvent (superseded by EventBridgeRule, see below) @@ -523,6 +527,11 @@ Example: detail: state: - terminated + DeadLetterConfig: + Type: SQS + RetryPolicy: + MaximumEventAgeInSeconds: 600 + MaximumRetryAttempts:3 ... Additional generated resources: @@ -532,6 +541,8 @@ CloudFormation Resource Type Logical ID ================================== ================================ AWS::Lambda::Permission MyFunction\ **OnTerminate**\ Permission AWS::Events::Rule MyFunction\ **OnTerminate** +AWS::SQS::Queue MyFunction\ **OnTerminate**\ Queue +AWS::SQS::QueuePolicy MyFunction\ **OnTerminate**\ QueuePolicy ================================== ================================ AWS::Serverless::Api diff --git a/samtranslator/model/eventbridge_utils.py b/samtranslator/model/eventbridge_utils.py new file mode 100644 index 0000000000..39bf407453 --- /dev/null +++ b/samtranslator/model/eventbridge_utils.py @@ -0,0 +1,54 @@ +from samtranslator.model.sqs import SQSQueue, SQSQueuePolicy, SQSQueuePolicies +from samtranslator.model.exceptions import InvalidEventException + + +class EventBridgeRuleUtils: + @staticmethod + def create_dead_letter_queue_with_policy(rule_logical_id, rule_arn, queue_logical_id=None): + resources = [] + + queue = SQSQueue(queue_logical_id or rule_logical_id + "Queue") + dlq_queue_arn = queue.get_runtime_attr("arn") + dlq_queue_url = queue.get_runtime_attr("queue_url") + + # grant necessary permission to Eventbridge Rule resource for sending messages to dead-letter queue + policy = SQSQueuePolicy(rule_logical_id + "QueuePolicy") + policy.PolicyDocument = SQSQueuePolicies.eventbridge_dlq_send_message_resource_based_policy( + rule_arn, dlq_queue_arn + ) + policy.Queues = [dlq_queue_url] + + resources.append(queue) + resources.append(policy) + + return resources + + @staticmethod + def validate_dlq_config(source_logical_id, dead_letter_config): + supported_types = ["SQS"] + is_arn_defined = "Arn" in dead_letter_config + is_type_defined = "Type" in dead_letter_config + if is_arn_defined and is_type_defined: + raise InvalidEventException( + source_logical_id, "You can either define 'Arn' or 'Type' property of DeadLetterConfig" + ) + if is_type_defined and dead_letter_config.get("Type") not in supported_types: + raise InvalidEventException( + source_logical_id, + "The only valid value for 'Type' property of DeadLetterConfig is 'SQS'", + ) + if not is_arn_defined and not is_type_defined: + raise InvalidEventException(source_logical_id, "No 'Arn' or 'Type' property provided for DeadLetterConfig") + + @staticmethod + def get_dlq_queue_arn_and_resources(cw_event_source, source_arn): + """returns dlq queue arn and dlq_resources, assuming cw_event_source.DeadLetterConfig has been validated""" + dlq_queue_arn = cw_event_source.DeadLetterConfig.get("Arn") + if dlq_queue_arn is not None: + return dlq_queue_arn, [] + queue_logical_id = cw_event_source.DeadLetterConfig.get("QueueLogicalId") + dlq_resources = EventBridgeRuleUtils.create_dead_letter_queue_with_policy( + cw_event_source.logical_id, source_arn, queue_logical_id + ) + dlq_queue_arn = dlq_resources[0].get_runtime_attr("arn") + return dlq_queue_arn, dlq_resources diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index 170452f78a..accce6512a 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -12,6 +12,7 @@ from samtranslator.model.events import EventsRule from samtranslator.model.eventsources.pull import SQS from samtranslator.model.sqs import SQSQueue, SQSQueuePolicy, SQSQueuePolicies +from samtranslator.model.eventbridge_utils import EventBridgeRuleUtils from samtranslator.model.iot import IotTopicRule from samtranslator.model.cognito import CognitoUserPool from samtranslator.translator import logical_id_generator @@ -94,6 +95,8 @@ class Schedule(PushEventSource): "Enabled": PropertyType(False, is_type(bool)), "Name": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), } def to_cloudformation(self, **kwargs): @@ -118,16 +121,23 @@ def to_cloudformation(self, **kwargs): events_rule.State = "ENABLED" if self.Enabled else "DISABLED" events_rule.Name = self.Name events_rule.Description = self.Description - events_rule.Targets = [self._construct_target(function)] source_arn = events_rule.get_runtime_attr("arn") + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + + events_rule.Targets = [self._construct_target(function, dlq_queue_arn)] + if CONDITION in function.resource_attributes: events_rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION]) resources.append(self._construct_permission(function, source_arn=source_arn)) return resources - def _construct_target(self, function): + def _construct_target(self, function, dead_letter_queue_arn=None): """Constructs the Target property for the EventBridge Rule. :returns: the Target property @@ -137,6 +147,12 @@ def _construct_target(self, function): if self.Input is not None: target["Input"] = self.Input + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target @@ -148,6 +164,8 @@ class CloudWatchEvent(PushEventSource): property_types = { "EventBusName": PropertyType(False, is_str()), "Pattern": PropertyType(False, is_type(dict)), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), "Input": PropertyType(False, is_str()), "InputPath": PropertyType(False, is_str()), "Target": PropertyType(False, is_type(dict)), @@ -171,18 +189,24 @@ def to_cloudformation(self, **kwargs): events_rule = EventsRule(self.logical_id) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern - events_rule.Targets = [self._construct_target(function)] + source_arn = events_rule.get_runtime_attr("arn") + + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + + events_rule.Targets = [self._construct_target(function, dlq_queue_arn)] if CONDITION in function.resource_attributes: events_rule.set_resource_attribute(CONDITION, function.resource_attributes[CONDITION]) resources.append(events_rule) - - source_arn = events_rule.get_runtime_attr("arn") resources.append(self._construct_permission(function, source_arn=source_arn)) return resources - def _construct_target(self, function): + def _construct_target(self, function, dead_letter_queue_arn=None): """Constructs the Target property for the CloudWatch Events/EventBridge Rule. :returns: the Target property @@ -195,6 +219,13 @@ def _construct_target(self, function): if self.InputPath is not None: target["InputPath"] = self.InputPath + + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target diff --git a/samtranslator/model/sqs.py b/samtranslator/model/sqs.py index b2691d309f..cd92933dbc 100644 --- a/samtranslator/model/sqs.py +++ b/samtranslator/model/sqs.py @@ -19,8 +19,8 @@ class SQSQueuePolicy(Resource): class SQSQueuePolicies: - @classmethod - def sns_topic_send_message_role_policy(cls, topic_arn, queue_arn): + @staticmethod + def sns_topic_send_message_role_policy(topic_arn, queue_arn): document = { "Version": "2012-10-17", "Statement": [ @@ -34,3 +34,19 @@ def sns_topic_send_message_role_policy(cls, topic_arn, queue_arn): ], } return document + + @staticmethod + def eventbridge_dlq_send_message_resource_based_policy(rule_arn, queue_arn): + document = { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Effect": "Allow", + "Principal": {"Service": "events.amazonaws.com"}, + "Resource": queue_arn, + "Condition": {"ArnEquals": {"aws:SourceArn": rule_arn}}, + } + ], + } + return document diff --git a/samtranslator/model/stepfunctions/events.py b/samtranslator/model/stepfunctions/events.py index e02332e20b..88fe60cfe8 100644 --- a/samtranslator/model/stepfunctions/events.py +++ b/samtranslator/model/stepfunctions/events.py @@ -8,6 +8,7 @@ from samtranslator.model.intrinsics import fnSub from samtranslator.translator import logical_id_generator from samtranslator.model.exceptions import InvalidEventException, InvalidResourceException +from samtranslator.model.eventbridge_utils import EventBridgeRuleUtils from samtranslator.translator.arn_generator import ArnGenerator from samtranslator.swagger.swagger import SwaggerEditor from samtranslator.open_api.open_api import OpenApiEditor @@ -81,6 +82,8 @@ class Schedule(EventSource): "Enabled": PropertyType(False, is_type(bool)), "Name": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), } def to_cloudformation(self, resource, **kwargs): @@ -107,11 +110,18 @@ def to_cloudformation(self, resource, **kwargs): role = self._construct_role(resource, permissions_boundary) resources.append(role) - events_rule.Targets = [self._construct_target(resource, role)] + + source_arn = events_rule.get_runtime_attr("arn") + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)] return resources - def _construct_target(self, resource, role): + def _construct_target(self, resource, role, dead_letter_queue_arn=None): """Constructs the Target property for the EventBridge Rule. :returns: the Target property @@ -125,6 +135,12 @@ def _construct_target(self, resource, role): if self.Input is not None: target["Input"] = self.Input + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target @@ -138,6 +154,8 @@ class CloudWatchEvent(EventSource): "Pattern": PropertyType(False, is_type(dict)), "Input": PropertyType(False, is_str()), "InputPath": PropertyType(False, is_str()), + "DeadLetterConfig": PropertyType(False, is_type(dict)), + "RetryPolicy": PropertyType(False, is_type(dict)), } def to_cloudformation(self, resource, **kwargs): @@ -162,11 +180,19 @@ def to_cloudformation(self, resource, **kwargs): role = self._construct_role(resource, permissions_boundary) resources.append(role) - events_rule.Targets = [self._construct_target(resource, role)] + + source_arn = events_rule.get_runtime_attr("arn") + dlq_queue_arn = None + if self.DeadLetterConfig is not None: + EventBridgeRuleUtils.validate_dlq_config(self.logical_id, self.DeadLetterConfig) + dlq_queue_arn, dlq_resources = EventBridgeRuleUtils.get_dlq_queue_arn_and_resources(self, source_arn) + resources.extend(dlq_resources) + + events_rule.Targets = [self._construct_target(resource, role, dlq_queue_arn)] return resources - def _construct_target(self, resource, role): + def _construct_target(self, resource, role, dead_letter_queue_arn=None): """Constructs the Target property for the CloudWatch Events/EventBridge Rule. :returns: the Target property @@ -182,6 +208,13 @@ def _construct_target(self, resource, role): if self.InputPath is not None: target["InputPath"] = self.InputPath + + if self.DeadLetterConfig is not None: + target["DeadLetterConfig"] = {"Arn": dead_letter_queue_arn} + + if self.RetryPolicy is not None: + target["RetryPolicy"] = self.RetryPolicy + return target diff --git a/samtranslator/validator/sam_schema/schema.json b/samtranslator/validator/sam_schema/schema.json index bff1d9ea32..72df7539e6 100644 --- a/samtranslator/validator/sam_schema/schema.json +++ b/samtranslator/validator/sam_schema/schema.json @@ -368,6 +368,34 @@ }, "Pattern": { "type": "object" + }, + "DeadLetterConfig": { + "additionalProperties": false, + "properties": { + "Arn": { + "type": "string" + }, + "Type": { + "type": "string" + }, + "QueueLogicalId": { + "type": "string" + } + }, + "type": "object" + }, + "RetryPolicy": { + "additionalProperties": false, + "minProperties": 1, + "properties": { + "MaximumEventAgeInSeconds": { + "type": "number" + }, + "MaximumRetryAttempts": { + "type": "number" + } + }, + "type": "object" } }, "required": [ diff --git a/tests/model/eventsources/test_eventbridge_rule_source.py b/tests/model/eventsources/test_eventbridge_rule_source.py new file mode 100644 index 0000000000..7e67edc8dc --- /dev/null +++ b/tests/model/eventsources/test_eventbridge_rule_source.py @@ -0,0 +1,78 @@ +from mock import Mock, patch +from unittest import TestCase + +from samtranslator.model.eventsources.push import EventBridgeRule +from samtranslator.model.lambda_ import LambdaFunction +from samtranslator.model.exceptions import InvalidEventException + + +class EventBridgeRuleSourceTests(TestCase): + def setUp(self): + self.logical_id = "EventBridgeRule" + self.func = LambdaFunction("func") + + self.eb_event_source = EventBridgeRule(self.logical_id) + self.eb_event_source.Pattern = {"detail": {"state": ["terminated"]}} + + def test_target_id_when_not_provided(self): + cfn = self.eb_event_source.to_cloudformation(function=self.func) + target_id = cfn[0].Targets[0]["Id"] + self.assertEqual(target_id, "{}{}".format(self.logical_id, "LambdaTarget")) + + def test_target_id_when_provided(self): + self.eb_event_source.Target = {"Id": "MyTargetId"} + cfn = self.eb_event_source.to_cloudformation(function=self.func) + target_id = cfn[0].Targets[0]["Id"] + self.assertEqual(target_id, "MyTargetId") + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.eb_event_source.RetryPolicy = retry_policy + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.eb_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.eb_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.eb_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.eb_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.eb_event_source.DeadLetterConfig = dead_letter_config + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[2] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.eb_event_source.DeadLetterConfig = dead_letter_config + resources = self.eb_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[2] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/model/eventsources/test_schedule_event_source.py b/tests/model/eventsources/test_schedule_event_source.py new file mode 100644 index 0000000000..a1be20378e --- /dev/null +++ b/tests/model/eventsources/test_schedule_event_source.py @@ -0,0 +1,66 @@ +from mock import Mock, patch +from unittest import TestCase + +from samtranslator.model.eventsources.push import Schedule +from samtranslator.model.lambda_ import LambdaFunction +from samtranslator.model.exceptions import InvalidEventException + + +class ScheduleEventSource(TestCase): + def setUp(self): + self.logical_id = "ScheduleEvent" + self.schedule_event_source = Schedule(self.logical_id) + self.schedule_event_source.Schedule = "rate(1 minute)" + self.func = LambdaFunction("func") + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.schedule_event_source.RetryPolicy = retry_policy + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(function=self.func) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(function=self.func) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/model/stepfunctions/test_cloudwatchevents_event.py b/tests/model/stepfunctions/test_cloudwatchevents_event.py index d60241cfa0..33c74928e9 100644 --- a/tests/model/stepfunctions/test_cloudwatchevents_event.py +++ b/tests/model/stepfunctions/test_cloudwatchevents_event.py @@ -1,6 +1,7 @@ from mock import Mock from unittest import TestCase from samtranslator.model.stepfunctions.events import CloudWatchEvent +from samtranslator.model.exceptions import InvalidEventException class CloudWatchEventsEventSource(TestCase): @@ -94,3 +95,55 @@ def test_to_cloudformation_with_eventbus_name(self): self.assertEqual(len(resources), 2) event_rule = resources[0] self.assertEqual(event_rule.Targets[0]["Input"], input_to_service) + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.cwe_event_source.RetryPolicy = retry_policy + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.cwe_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.cwe_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.cwe_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.cwe_event_source.DeadLetterConfig = dead_letter_config + resources = self.cwe_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/model/stepfunctions/test_schedule_event.py b/tests/model/stepfunctions/test_schedule_event.py index 4082dc91a2..e426f93056 100644 --- a/tests/model/stepfunctions/test_schedule_event.py +++ b/tests/model/stepfunctions/test_schedule_event.py @@ -1,6 +1,7 @@ from mock import Mock from unittest import TestCase from samtranslator.model.stepfunctions.events import Schedule +from samtranslator.model.exceptions import InvalidEventException class ScheduleEventSource(TestCase): @@ -85,3 +86,55 @@ def test_to_cloudformation_with_input(self): self.assertEqual(len(resources), 2) event_rule = resources[0] self.assertEqual(event_rule.Targets[0]["Input"], input_to_service) + + def test_to_cloudformation_with_retry_policy(self): + retry_policy = {"MaximumRetryAttempts": "10", "MaximumEventAgeInSeconds": "300"} + self.schedule_event_source.RetryPolicy = retry_policy + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["RetryPolicy"], retry_policy) + + def test_to_cloudformation_with_dlq_arn_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 2) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config) + + def test_to_cloudformation_invalid_both_dlq_arn_and_type_provided(self): + dead_letter_config = {"Arn": "DeadLetterQueueArn", "Type": "SQS"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_invalid_dlq_type_provided(self): + dead_letter_config = {"Type": "SNS", "QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_missing_dlq_type_or_arn(self): + dead_letter_config = {"QueueLogicalId": "MyDLQ"} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + with self.assertRaises(InvalidEventException): + self.schedule_event_source.to_cloudformation(resource=self.state_machine) + + def test_to_cloudformation_with_dlq_generated(self): + dead_letter_config = {"Type": "SQS"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": [self.logical_id + "Queue", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) + + def test_to_cloudformation_with_dlq_generated_with_custom_logical_id(self): + dead_letter_config = {"Type": "SQS", "QueueLogicalId": "MyDLQ"} + dead_letter_config_translated = {"Arn": {"Fn::GetAtt": ["MyDLQ", "Arn"]}} + self.schedule_event_source.DeadLetterConfig = dead_letter_config + resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine) + self.assertEqual(len(resources), 4) + event_rule = resources[0] + self.assertEqual(event_rule.Targets[0]["DeadLetterConfig"], dead_letter_config_translated) diff --git a/tests/translator/input/error_function_with_cwe_both_dlq_property_provided.yaml b/tests/translator/input/error_function_with_cwe_both_dlq_property_provided.yaml new file mode 100644 index 0000000000..28a4063eba --- /dev/null +++ b/tests/translator/input/error_function_with_cwe_both_dlq_property_provided.yaml @@ -0,0 +1,19 @@ +Resources: + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: CloudWatchEvent + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Arn: DlqArn + Type: SQS \ No newline at end of file diff --git a/tests/translator/input/error_function_with_cwe_invalid_dlq_type.yaml b/tests/translator/input/error_function_with_cwe_invalid_dlq_type.yaml new file mode 100644 index 0000000000..924f74dcee --- /dev/null +++ b/tests/translator/input/error_function_with_cwe_invalid_dlq_type.yaml @@ -0,0 +1,18 @@ +Resources: + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: queue \ No newline at end of file diff --git a/tests/translator/input/error_function_with_cwe_missing_dlq_property.yaml b/tests/translator/input/error_function_with_cwe_missing_dlq_property.yaml new file mode 100644 index 0000000000..fc0eb1b575 --- /dev/null +++ b/tests/translator/input/error_function_with_cwe_missing_dlq_property.yaml @@ -0,0 +1,18 @@ +Resources: + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + QueueLogicalId: MyDlqId \ No newline at end of file diff --git a/tests/translator/input/error_function_with_schedule_both_dlq_property_provided.yaml b/tests/translator/input/error_function_with_schedule_both_dlq_property_provided.yaml new file mode 100644 index 0000000000..cc762882f8 --- /dev/null +++ b/tests/translator/input/error_function_with_schedule_both_dlq_property_provided.yaml @@ -0,0 +1,15 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + Type: SQS + Arn: MyDlqArn \ No newline at end of file diff --git a/tests/translator/input/error_function_with_schedule_invalid_dlq_type.yaml b/tests/translator/input/error_function_with_schedule_invalid_dlq_type.yaml new file mode 100644 index 0000000000..e2add19d3f --- /dev/null +++ b/tests/translator/input/error_function_with_schedule_invalid_dlq_type.yaml @@ -0,0 +1,14 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + Type: SNS \ No newline at end of file diff --git a/tests/translator/input/error_function_with_schedule_missing_dlq_property.yaml b/tests/translator/input/error_function_with_schedule_missing_dlq_property.yaml new file mode 100644 index 0000000000..542e05513a --- /dev/null +++ b/tests/translator/input/error_function_with_schedule_missing_dlq_property.yaml @@ -0,0 +1,14 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + QueueLogicalId: MyDlqId diff --git a/tests/translator/input/error_state_machine_with_cwe_both_dlq_property_provided.yaml b/tests/translator/input/error_state_machine_with_cwe_both_dlq_property_provided.yaml new file mode 100644 index 0000000000..15b0521816 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_cwe_both_dlq_property_provided.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: SQS + Arn: MyDlqArn diff --git a/tests/translator/input/error_state_machine_with_cwe_invalid_dlq_type.yaml b/tests/translator/input/error_state_machine_with_cwe_invalid_dlq_type.yaml new file mode 100644 index 0000000000..47c7bacd45 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_cwe_invalid_dlq_type.yaml @@ -0,0 +1,16 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: EventBus diff --git a/tests/translator/input/error_state_machine_with_cwe_missing_dlq_property.yaml b/tests/translator/input/error_state_machine_with_cwe_missing_dlq_property.yaml new file mode 100644 index 0000000000..889306b0c5 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_cwe_missing_dlq_property.yaml @@ -0,0 +1,16 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: CloudWatchEvent + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + QueuelogicalId: MyDlqId diff --git a/tests/translator/input/error_state_machine_with_schedule_both_dlq_property_provided.yaml b/tests/translator/input/error_state_machine_with_schedule_both_dlq_property_provided.yaml new file mode 100644 index 0000000000..f4faf5c587 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_schedule_both_dlq_property_provided.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + Arn: MyDlqArn + Type: SQS \ No newline at end of file diff --git a/tests/translator/input/error_state_machine_with_schedule_invalid_dlq_type.yaml b/tests/translator/input/error_state_machine_with_schedule_invalid_dlq_type.yaml new file mode 100644 index 0000000000..b052c599b1 --- /dev/null +++ b/tests/translator/input/error_state_machine_with_schedule_invalid_dlq_type.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + Type: SNS + \ No newline at end of file diff --git a/tests/translator/input/error_state_machine_with_schedule_missing_dlq_property.yaml b/tests/translator/input/error_state_machine_with_schedule_missing_dlq_property.yaml new file mode 100644 index 0000000000..cabfb9bcee --- /dev/null +++ b/tests/translator/input/error_state_machine_with_schedule_missing_dlq_property.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + QueueLogicalId: MyDlq + \ No newline at end of file diff --git a/tests/translator/input/eventbridgerule_with_dlq.yaml b/tests/translator/input/eventbridgerule_with_dlq.yaml new file mode 100644 index 0000000000..7bb7dc59db --- /dev/null +++ b/tests/translator/input/eventbridgerule_with_dlq.yaml @@ -0,0 +1,31 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + DeadLetterConfig: + Type: SQS + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Arn: ARN \ No newline at end of file diff --git a/tests/translator/input/eventbridgerule_with_retry_policy.yaml b/tests/translator/input/eventbridgerule_with_retry_policy.yaml new file mode 100644 index 0000000000..ed66964aff --- /dev/null +++ b/tests/translator/input/eventbridgerule_with_retry_policy.yaml @@ -0,0 +1,32 @@ +Resources: + ScheduledFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + Schedule: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + RetryPolicy: + MaximumRetryAttempts: 3 + TriggeredFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip?versionId=3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO + Handler: hello.handler + Runtime: python2.7 + Events: + OnTerminate: + Type: EventBridgeRule + Properties: + EventBusName: ExternalEventBridge + Pattern: + detail: + state: + - terminated + RetryPolicy: + MaximumRetryAttempts: 3 + MaximumEventAgeInSeconds: 200 \ No newline at end of file diff --git a/tests/translator/input/state_machine_with_eb_dlq.yaml b/tests/translator/input/state_machine_with_eb_dlq.yaml new file mode 100644 index 0000000000..90721bd207 --- /dev/null +++ b/tests/translator/input/state_machine_with_eb_dlq.yaml @@ -0,0 +1,16 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Arn: TestDlqArn \ No newline at end of file diff --git a/tests/translator/input/state_machine_with_eb_dlq_generated.yaml b/tests/translator/input/state_machine_with_eb_dlq_generated.yaml new file mode 100644 index 0000000000..9f8d746770 --- /dev/null +++ b/tests/translator/input/state_machine_with_eb_dlq_generated.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + DeadLetterConfig: + Type: SQS + QueueLogicalId: TestDLQ \ No newline at end of file diff --git a/tests/translator/input/state_machine_with_eb_retry_policy.yaml b/tests/translator/input/state_machine_with_eb_retry_policy.yaml new file mode 100644 index 0000000000..21dc2d921d --- /dev/null +++ b/tests/translator/input/state_machine_with_eb_retry_policy.yaml @@ -0,0 +1,17 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + CWEvent: + Type: EventBridgeRule + Properties: + Pattern: + detail: + state: + - terminated + RetryPolicy: + MaximumRetryAttempts: 5 + MaximumEventAgeInSeconds: 300 \ No newline at end of file diff --git a/tests/translator/input/state_machine_with_schedule_dlq_retry_policy.yaml b/tests/translator/input/state_machine_with_schedule_dlq_retry_policy.yaml new file mode 100644 index 0000000000..9f7385f901 --- /dev/null +++ b/tests/translator/input/state_machine_with_schedule_dlq_retry_policy.yaml @@ -0,0 +1,19 @@ +Resources: + StateMachine: + Type: 'AWS::Serverless::StateMachine' + Properties: + DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json + Role: arn:aws:iam::123456123456:role/service-role/SampleRole + Events: + ScheduleEvent: + Type: Schedule + Properties: + Schedule: 'rate(1 minute)' + Name: TestSchedule + Description: test schedule + Enabled: False + DeadLetterConfig: + Arn: Arn + RetryPolicy: + MaximumRetryAttempts: 5 + MaximumEventAgeInSeconds: 300 \ No newline at end of file diff --git a/tests/translator/output/aws-cn/eventbridgerule_with_dlq.json b/tests/translator/output/aws-cn/eventbridgerule_with_dlq.json new file mode 100644 index 0000000000..6f6fa0460f --- /dev/null +++ b/tests/translator/output/aws-cn/eventbridgerule_with_dlq.json @@ -0,0 +1,238 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "ScheduledFunctionScheduleQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": "ARN" + }, + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + } + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + } + }, + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/eventbridgerule_with_retry_policy.json b/tests/translator/output/aws-cn/eventbridgerule_with_retry_policy.json new file mode 100644 index 0000000000..d1ab9d8487 --- /dev/null +++ b/tests/translator/output/aws-cn/eventbridgerule_with_retry_policy.json @@ -0,0 +1,189 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 200, + "MaximumRetryAttempts": 3 + } + } + ] + } + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_eb_dlq.json b/tests/translator/output/aws-cn/state_machine_with_eb_dlq.json new file mode 100644 index 0000000000..e49e64256f --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_eb_dlq.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "TestDlqArn" + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_eb_dlq_generated.json b/tests/translator/output/aws-cn/state_machine_with_eb_dlq_generated.json new file mode 100644 index 0000000000..7e57f89263 --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_eb_dlq_generated.json @@ -0,0 +1,133 @@ +{ + "Resources": { + "TestDLQ": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + } + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachineCWEventQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "TestDLQ" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "StateMachineCWEvent", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_eb_retry_policy.json b/tests/translator/output/aws-cn/state_machine_with_eb_retry_policy.json new file mode 100644 index 0000000000..bff94e177a --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_eb_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/state_machine_with_schedule_dlq_retry_policy.json b/tests/translator/output/aws-cn/state_machine_with_schedule_dlq_retry_policy.json new file mode 100644 index 0000000000..5d49175a96 --- /dev/null +++ b/tests/translator/output/aws-cn/state_machine_with_schedule_dlq_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "DISABLED", + "ScheduleExpression": "rate(1 minute)", + "Name": "TestSchedule", + "Description": "test schedule", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "Arn" + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/eventbridgerule_with_dlq.json b/tests/translator/output/aws-us-gov/eventbridgerule_with_dlq.json new file mode 100644 index 0000000000..b5dd4901b8 --- /dev/null +++ b/tests/translator/output/aws-us-gov/eventbridgerule_with_dlq.json @@ -0,0 +1,238 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "ScheduledFunctionScheduleQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": "ARN" + }, + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + } + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + } + }, + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/eventbridgerule_with_retry_policy.json b/tests/translator/output/aws-us-gov/eventbridgerule_with_retry_policy.json new file mode 100644 index 0000000000..d904833c33 --- /dev/null +++ b/tests/translator/output/aws-us-gov/eventbridgerule_with_retry_policy.json @@ -0,0 +1,189 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 200, + "MaximumRetryAttempts": 3 + } + } + ] + } + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq.json b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq.json new file mode 100644 index 0000000000..e49e64256f --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "TestDlqArn" + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq_generated.json b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq_generated.json new file mode 100644 index 0000000000..7e57f89263 --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_eb_dlq_generated.json @@ -0,0 +1,133 @@ +{ + "Resources": { + "TestDLQ": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + } + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachineCWEventQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "TestDLQ" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "StateMachineCWEvent", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_eb_retry_policy.json b/tests/translator/output/aws-us-gov/state_machine_with_eb_retry_policy.json new file mode 100644 index 0000000000..bff94e177a --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_eb_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/state_machine_with_schedule_dlq_retry_policy.json b/tests/translator/output/aws-us-gov/state_machine_with_schedule_dlq_retry_policy.json new file mode 100644 index 0000000000..5d49175a96 --- /dev/null +++ b/tests/translator/output/aws-us-gov/state_machine_with_schedule_dlq_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "DISABLED", + "ScheduleExpression": "rate(1 minute)", + "Name": "TestSchedule", + "Description": "test schedule", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "Arn" + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_cwe_both_dlq_property_provided.json b/tests/translator/output/error_function_with_cwe_both_dlq_property_provided.json new file mode 100644 index 0000000000..541990f400 --- /dev/null +++ b/tests/translator/output/error_function_with_cwe_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [TriggeredFunction] is invalid. Event with id [TriggeredFunctionOnTerminate] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_cwe_invalid_dlq_type.json b/tests/translator/output/error_function_with_cwe_invalid_dlq_type.json new file mode 100644 index 0000000000..ded9379e67 --- /dev/null +++ b/tests/translator/output/error_function_with_cwe_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [TriggeredFunction] is invalid. Event with id [TriggeredFunctionOnTerminate] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_cwe_missing_dlq_property.json b/tests/translator/output/error_function_with_cwe_missing_dlq_property.json new file mode 100644 index 0000000000..edf6a7fd79 --- /dev/null +++ b/tests/translator/output/error_function_with_cwe_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [TriggeredFunction] is invalid. Event with id [TriggeredFunctionOnTerminate] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_schedule_both_dlq_property_provided.json b/tests/translator/output/error_function_with_schedule_both_dlq_property_provided.json new file mode 100644 index 0000000000..9a90e5cf99 --- /dev/null +++ b/tests/translator/output/error_function_with_schedule_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ScheduledFunction] is invalid. Event with id [ScheduledFunctionSchedule] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_schedule_invalid_dlq_type.json b/tests/translator/output/error_function_with_schedule_invalid_dlq_type.json new file mode 100644 index 0000000000..d779e7ea0b --- /dev/null +++ b/tests/translator/output/error_function_with_schedule_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ScheduledFunction] is invalid. Event with id [ScheduledFunctionSchedule] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_function_with_schedule_missing_dlq_property.json b/tests/translator/output/error_function_with_schedule_missing_dlq_property.json new file mode 100644 index 0000000000..bb004d8d87 --- /dev/null +++ b/tests/translator/output/error_function_with_schedule_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ScheduledFunction] is invalid. Event with id [ScheduledFunctionSchedule] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_cwe_both_dlq_property_provided.json b/tests/translator/output/error_state_machine_with_cwe_both_dlq_property_provided.json new file mode 100644 index 0000000000..10180f3571 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_cwe_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineCWEvent] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_cwe_invalid_dlq_type.json b/tests/translator/output/error_state_machine_with_cwe_invalid_dlq_type.json new file mode 100644 index 0000000000..3e132e754b --- /dev/null +++ b/tests/translator/output/error_state_machine_with_cwe_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineCWEvent] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_cwe_missing_dlq_property.json b/tests/translator/output/error_state_machine_with_cwe_missing_dlq_property.json new file mode 100644 index 0000000000..39b0165200 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_cwe_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineCWEvent] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_schedule_both_dlq_property_provided.json b/tests/translator/output/error_state_machine_with_schedule_both_dlq_property_provided.json new file mode 100644 index 0000000000..932c2adc7a --- /dev/null +++ b/tests/translator/output/error_state_machine_with_schedule_both_dlq_property_provided.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineScheduleEvent] is invalid. You can either define 'Arn' or 'Type' property of DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_schedule_invalid_dlq_type.json b/tests/translator/output/error_state_machine_with_schedule_invalid_dlq_type.json new file mode 100644 index 0000000000..c429367c2d --- /dev/null +++ b/tests/translator/output/error_state_machine_with_schedule_invalid_dlq_type.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineScheduleEvent] is invalid. The only valid value for 'Type' property of DeadLetterConfig is 'SQS'" +} \ No newline at end of file diff --git a/tests/translator/output/error_state_machine_with_schedule_missing_dlq_property.json b/tests/translator/output/error_state_machine_with_schedule_missing_dlq_property.json new file mode 100644 index 0000000000..ac66076034 --- /dev/null +++ b/tests/translator/output/error_state_machine_with_schedule_missing_dlq_property.json @@ -0,0 +1,3 @@ +{ + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Event with id [StateMachineScheduleEvent] is invalid. No 'Arn' or 'Type' property provided for DeadLetterConfig" +} \ No newline at end of file diff --git a/tests/translator/output/eventbridgerule_with_dlq.json b/tests/translator/output/eventbridgerule_with_dlq.json new file mode 100644 index 0000000000..2d5c96a915 --- /dev/null +++ b/tests/translator/output/eventbridgerule_with_dlq.json @@ -0,0 +1,238 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "ScheduledFunctionScheduleQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": "ARN" + }, + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + } + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionScheduleQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunctionScheduleQueue", + "Arn" + ] + } + }, + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/eventbridgerule_with_retry_policy.json b/tests/translator/output/eventbridgerule_with_retry_policy.json new file mode 100644 index 0000000000..2cc7e5d9c1 --- /dev/null +++ b/tests/translator/output/eventbridgerule_with_retry_policy.json @@ -0,0 +1,189 @@ +{ + "Resources": { + "ScheduledFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminatePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "TriggeredFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "TriggeredFunctionOnTerminate", + "Arn" + ] + } + } + }, + "ScheduledFunctionSchedulePermission": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", + "FunctionName": { + "Ref": "ScheduledFunction" + }, + "SourceArn": { + "Fn::GetAtt": [ + "ScheduledFunctionSchedule", + "Arn" + ] + } + } + }, + "ScheduledFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "ScheduledFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionOnTerminate": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "EventBusName": "ExternalEventBridge", + "Targets": [ + { + "Id": "TriggeredFunctionOnTerminateLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "TriggeredFunction", + "Arn" + ] + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 200, + "MaximumRetryAttempts": 3 + } + } + ] + } + }, + "TriggeredFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Handler": "hello.handler", + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip", + "S3ObjectVersion": "3Tcgv52_0GaDvhDva4YciYeqRyPnpIcO" + }, + "Role": { + "Fn::GetAtt": [ + "TriggeredFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "TriggeredFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Value": "SAM", + "Key": "lambda:createdBy" + } + ] + } + }, + "ScheduledFunctionSchedule": { + "Type": "AWS::Events::Rule", + "Properties": { + "ScheduleExpression": "rate(1 minute)", + "Targets": [ + { + "Id": "ScheduledFunctionScheduleLambdaTarget", + "Arn": { + "Fn::GetAtt": [ + "ScheduledFunction", + "Arn" + ] + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_eb_dlq.json b/tests/translator/output/state_machine_with_eb_dlq.json new file mode 100644 index 0000000000..e49e64256f --- /dev/null +++ b/tests/translator/output/state_machine_with_eb_dlq.json @@ -0,0 +1,86 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "TestDlqArn" + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_eb_dlq_generated.json b/tests/translator/output/state_machine_with_eb_dlq_generated.json new file mode 100644 index 0000000000..7e57f89263 --- /dev/null +++ b/tests/translator/output/state_machine_with_eb_dlq_generated.json @@ -0,0 +1,133 @@ +{ + "Resources": { + "TestDLQ": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + } + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + } + } + ] + } + }, + "StateMachineCWEventQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "TestDLQ" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "TestDLQ", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Fn::GetAtt": [ + "StateMachineCWEvent", + "Arn" + ] + } + } + }, + "Principal": { + "Service": "events.amazonaws.com" + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_eb_retry_policy.json b/tests/translator/output/state_machine_with_eb_retry_policy.json new file mode 100644 index 0000000000..bff94e177a --- /dev/null +++ b/tests/translator/output/state_machine_with_eb_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineCWEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "EventPattern": { + "detail": { + "state": [ + "terminated" + ] + } + }, + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineCWEventRole", + "Arn" + ] + }, + "Id": "StateMachineCWEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + }, + "StateMachineCWEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineCWEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/state_machine_with_schedule_dlq_retry_policy.json b/tests/translator/output/state_machine_with_schedule_dlq_retry_policy.json new file mode 100644 index 0000000000..5d49175a96 --- /dev/null +++ b/tests/translator/output/state_machine_with_schedule_dlq_retry_policy.json @@ -0,0 +1,87 @@ +{ + "Resources": { + "StateMachineScheduleEvent": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "DISABLED", + "ScheduleExpression": "rate(1 minute)", + "Name": "TestSchedule", + "Description": "test schedule", + "Targets": [ + { + "RoleArn": { + "Fn::GetAtt": [ + "StateMachineScheduleEventRole", + "Arn" + ] + }, + "DeadLetterConfig": { + "Arn": "Arn" + }, + "Id": "StateMachineScheduleEventStepFunctionsTarget", + "Arn": { + "Ref": "StateMachine" + }, + "RetryPolicy": { + "MaximumEventAgeInSeconds": 300, + "MaximumRetryAttempts": 5 + } + } + ] + } + }, + "StateMachineScheduleEventRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Policies": [ + { + "PolicyName": "StateMachineScheduleEventRoleStartExecutionPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": "states:StartExecution", + "Resource": { + "Ref": "StateMachine" + }, + "Effect": "Allow" + } + ] + } + } + ], + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "events.amazonaws.com" + ] + } + } + ] + } + } + }, + "StateMachine": { + "Type": "AWS::StepFunctions::StateMachine", + "Properties": { + "RoleArn": "arn:aws:iam::123456123456:role/service-role/SampleRole", + "DefinitionS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "my_state_machine.asl.json" + }, + "Tags": [ + { + "Value": "SAM", + "Key": "stateMachine:createdBy" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 7318e566e3..28dfce2462 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -153,6 +153,8 @@ class TestTranslatorEndToEnd(TestCase): "basic_layer", "cloudwatchevent", "eventbridgerule", + "eventbridgerule_with_dlq", + "eventbridgerule_with_retry_policy", "eventbridgerule_schedule_properties", "cloudwatch_logs_with_ref", "cloudwatchlog", @@ -294,7 +296,11 @@ class TestTranslatorEndToEnd(TestCase): "state_machine_with_managed_policy", "state_machine_with_condition", "state_machine_with_schedule", + "state_machine_with_schedule_dlq_retry_policy", "state_machine_with_cwe", + "state_machine_with_eb_retry_policy", + "state_machine_with_eb_dlq", + "state_machine_with_eb_dlq_generated", "state_machine_with_explicit_api", "state_machine_with_implicit_api", "state_machine_with_implicit_api_globals", @@ -576,6 +582,12 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_state_machine_with_no_api_authorizers", "error_state_machine_with_undefined_api_authorizer", "error_state_machine_with_invalid_default_authorizer", + "error_state_machine_with_schedule_invalid_dlq_type", + "error_state_machine_with_schedule_both_dlq_property_provided", + "error_state_machine_with_schedule_missing_dlq_property", + "error_state_machine_with_cwe_invalid_dlq_type", + "error_state_machine_with_cwe_both_dlq_property_provided", + "error_state_machine_with_cwe_missing_dlq_property", "error_cognito_userpool_duplicate_trigger", "error_cognito_userpool_not_string", "error_api_duplicate_methods_same_path", @@ -611,6 +623,12 @@ def _generate_new_deployment_hash(self, logical_id, dict_to_hash, rest_api_to_sw "error_function_with_deployment_preference_missing_alias", "error_function_with_invalid_deployment_preference_hook_property", "error_function_invalid_request_parameters", + "error_function_with_schedule_invalid_dlq_type", + "error_function_with_schedule_both_dlq_property_provided", + "error_function_with_schedule_missing_dlq_property", + "error_function_with_cwe_invalid_dlq_type", + "error_function_with_cwe_both_dlq_property_provided", + "error_function_with_cwe_missing_dlq_property", "error_invalid_logical_id", "error_layer_invalid_properties", "error_missing_broker", From acde9bc21f477e708152a829755023e869cf0c5c Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Wed, 2 Dec 2020 13:53:28 -0800 Subject: [PATCH 08/31] fix: propagate condition to sqs queue policy for sqssubscription (#1798) * fix: propagate condition to sqs queue policy for sqssubscription * Update unit test for function_event_conditions * Update black commands in Makefile to check only .py files * Update test with one more SNS event source with sqsSubscription set * Revert "Update black commands in Makefile to check only .py files" This reverts commit 115ff09cd1db5939d21ef4d3c8c8e5690629f9ab. --- samtranslator/model/eventsources/push.py | 11 +- .../input/function_event_conditions.yaml | 20 ++ .../aws-cn/function_event_conditions.json | 145 +++++++- .../aws-us-gov/function_event_conditions.json | 145 +++++++- .../output/function_event_conditions.json | 309 +++++++++++++----- 5 files changed, 542 insertions(+), 88 deletions(-) diff --git a/samtranslator/model/eventsources/push.py b/samtranslator/model/eventsources/push.py index accce6512a..d134f19402 100644 --- a/samtranslator/model/eventsources/push.py +++ b/samtranslator/model/eventsources/push.py @@ -438,7 +438,7 @@ def to_cloudformation(self, **kwargs): queue_arn = queue.get_runtime_attr("arn") queue_url = queue.get_runtime_attr("queue_url") - queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url) + queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url, function.resource_attributes) subscription = self._inject_subscription( "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes ) @@ -461,7 +461,9 @@ def to_cloudformation(self, **kwargs): batch_size = self.SqsSubscription.get("BatchSize", None) enabled = self.SqsSubscription.get("Enabled", None) - queue_policy = self._inject_sqs_queue_policy(self.Topic, queue_arn, queue_url, queue_policy_logical_id) + queue_policy = self._inject_sqs_queue_policy( + self.Topic, queue_arn, queue_url, function.resource_attributes, queue_policy_logical_id + ) subscription = self._inject_subscription( "sqs", queue_arn, self.Topic, self.Region, self.FilterPolicy, function.resource_attributes ) @@ -497,8 +499,11 @@ def _inject_sqs_event_source_mapping(self, function, role, queue_arn, batch_size event_source.Enabled = enabled or True return event_source.to_cloudformation(function=function, role=role) - def _inject_sqs_queue_policy(self, topic_arn, queue_arn, queue_url, logical_id=None): + def _inject_sqs_queue_policy(self, topic_arn, queue_arn, queue_url, resource_attributes, logical_id=None): policy = SQSQueuePolicy(logical_id or self.logical_id + "QueuePolicy") + if CONDITION in resource_attributes: + policy.set_resource_attribute(CONDITION, resource_attributes[CONDITION]) + policy.PolicyDocument = SQSQueuePolicies.sns_topic_send_message_role_policy(topic_arn, queue_arn) policy.Queues = [queue_url] return policy diff --git a/tests/translator/input/function_event_conditions.yaml b/tests/translator/input/function_event_conditions.yaml index 1a53dab8bb..f30751333c 100644 --- a/tests/translator/input/function_event_conditions.yaml +++ b/tests/translator/input/function_event_conditions.yaml @@ -79,6 +79,22 @@ Resources: Topic: Ref: Notifications + SNSTopicWithSQSSubscription: + Type: SNS + Properties: + Topic: + Ref: Notifications + SqsSubscription: + QueueArn: !GetAtt Queue.Arn + QueueUrl: !Ref Queue + + AnotherSNSWithSQSSubscription: + Type: SNS + Properties: + Topic: + Ref: Notifications + SqsSubscription: true + KinesisStream: Type: Kinesis Properties: @@ -99,3 +115,7 @@ Resources: Images: Type: AWS::S3::Bucket + + Queue: + Condition: MyCondition + Type: AWS::SQS::Queue diff --git a/tests/translator/output/aws-cn/function_event_conditions.json b/tests/translator/output/aws-cn/function_event_conditions.json index c6010f940e..274bbc5387 100644 --- a/tests/translator/output/aws-cn/function_event_conditions.json +++ b/tests/translator/output/aws-cn/function_event_conditions.json @@ -222,7 +222,8 @@ "ManagedPolicyArns": [ "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" ], "Tags": [ { @@ -459,6 +460,148 @@ "DependsOn": [ "FunctionOneImageBucketPermission" ] + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "Queue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "Queue": { + "Type": "AWS::SQS::Queue", + "Condition": "MyCondition" } } } diff --git a/tests/translator/output/aws-us-gov/function_event_conditions.json b/tests/translator/output/aws-us-gov/function_event_conditions.json index 2b1dea068d..e22637ef5f 100644 --- a/tests/translator/output/aws-us-gov/function_event_conditions.json +++ b/tests/translator/output/aws-us-gov/function_event_conditions.json @@ -222,7 +222,8 @@ "ManagedPolicyArns": [ "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" ], "Tags": [ { @@ -459,6 +460,148 @@ "DependsOn": [ "FunctionOneImageBucketPermission" ] + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "Queue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "Queue": { + "Type": "AWS::SQS::Queue", + "Condition": "MyCondition" } } } diff --git a/tests/translator/output/function_event_conditions.json b/tests/translator/output/function_event_conditions.json index a1c0e9a766..c6de703186 100644 --- a/tests/translator/output/function_event_conditions.json +++ b/tests/translator/output/function_event_conditions.json @@ -9,44 +9,44 @@ }, "Resources": { "MyAwesomeFunctionAliasLive": { - "Type": "AWS::Lambda::Alias", + "Type": "AWS::Lambda::Alias", "Condition": "MyCondition", "Properties": { "FunctionVersion": { "Fn::GetAtt": [ - "MyAwesomeFunctionVersion640128d35d", + "MyAwesomeFunctionVersion640128d35d", "Version" ] - }, + }, "FunctionName": { "Ref": "MyAwesomeFunction" - }, + }, "Name": "Live" } - }, + }, "MyAwesomeFunctionNotificationTopicPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "sns.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "sns.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Ref": "Notifications" } } }, "MyAwesomeFunctionCWEventPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "events.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::GetAtt": [ "MyAwesomeFunctionCWEvent", @@ -73,23 +73,23 @@ } }, "MyAwesomeFunctionDDBStream": { - "Type": "AWS::Lambda::EventSourceMapping", + "Type": "AWS::Lambda::EventSourceMapping", "Condition": "MyCondition", "Properties": { - "BatchSize": 200, - "EventSourceArn": "arn:aws:dynamodb:us-west-2:012345678901:table/TestTable/stream/2015-05-11T21:21:33.291", + "BatchSize": 200, + "EventSourceArn": "arn:aws:dynamodb:us-west-2:012345678901:table/TestTable/stream/2015-05-11T21:21:33.291", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "StartingPosition": "LATEST" } }, "MyAwesomeFunctionIoTRule": { - "Type": "AWS::IoT::TopicRule", + "Type": "AWS::IoT::TopicRule", "Condition": "MyCondition", "Properties": { "TopicRulePayload": { - "AwsIotSqlVersion": "beta", + "AwsIotSqlVersion": "beta", "Actions": [ { "Lambda": { @@ -98,39 +98,39 @@ } } } - ], - "RuleDisabled": false, + ], + "RuleDisabled": false, "Sql": "SELECT * FROM 'topic/test'" } } - }, + }, "MyAwesomeFunctionKinesisStream": { - "Type": "AWS::Lambda::EventSourceMapping", + "Type": "AWS::Lambda::EventSourceMapping", "Condition": "MyCondition", "Properties": { - "BatchSize": 100, - "EventSourceArn": "arn:aws:kinesis:us-west-2:012345678901:stream/my-stream", + "BatchSize": 100, + "EventSourceArn": "arn:aws:kinesis:us-west-2:012345678901:stream/my-stream", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "StartingPosition": "TRIM_HORIZON" } }, "MyAwesomeFunctionIoTRulePermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", + "Action": "lambda:InvokeFunction", "SourceAccount": { "Fn::Sub": "${AWS::AccountId}" - }, - "Principal": "iot.amazonaws.com", + }, + "Principal": "iot.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::Sub": [ - "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:rule/${RuleName}", + "arn:aws:iot:${AWS::Region}:${AWS::AccountId}:rule/${RuleName}", { "RuleName": { "Ref": "MyAwesomeFunctionIoTRule" @@ -139,58 +139,58 @@ ] } } - }, + }, "MyAwesomeFunctionNotificationTopic": { - "Type": "AWS::SNS::Subscription", + "Type": "AWS::SNS::Subscription", "Condition": "MyCondition", "Properties": { "Endpoint": { "Ref": "MyAwesomeFunctionAliasLive" - }, - "Protocol": "lambda", + }, + "Protocol": "lambda", "TopicArn": { "Ref": "Notifications" } } - }, + }, "MyAwesomeFunctionS3TriggerPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", + "Action": "lambda:InvokeFunction", "SourceAccount": { "Ref": "AWS::AccountId" - }, + }, "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "Principal": "s3.amazonaws.com" } }, "MyAwesomeFunctionCWLogPermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "logs.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "logs.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::Sub": [ - "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${__LogGroupName__}:*", + "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${__LogGroupName__}:*", { "__LogGroupName__": "MyLogGroup" } ] } } - }, + }, "MyAwesomeFunctionEBSchedule": { - "Type": "AWS::Events::Rule", + "Type": "AWS::Events::Rule", "Condition": "MyCondition", "Properties": { - "ScheduleExpression": "rate(1 minute)", + "ScheduleExpression": "rate(1 minute)", "Targets": [ { "Id": "MyAwesomeFunctionEBScheduleLambdaTarget", @@ -200,29 +200,30 @@ } ] } - }, + }, "MyAwesomeFunctionCWLog": { - "Type": "AWS::Logs::SubscriptionFilter", + "Type": "AWS::Logs::SubscriptionFilter", "Condition": "MyCondition", "Properties": { "DestinationArn": { "Ref": "MyAwesomeFunctionAliasLive" - }, - "FilterPattern": "My pattern", + }, + "FilterPattern": "My pattern", "LogGroupName": "MyLogGroup" - }, + }, "DependsOn": [ "MyAwesomeFunctionCWLogPermission" ] - }, + }, "MyAwesomeFunctionRole": { - "Type": "AWS::IAM::Role", + "Type": "AWS::IAM::Role", "Condition": "MyCondition", "Properties": { "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole" + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "arn:aws:iam::aws:policy/service-role/AWSLambdaSQSQueueExecutionRole" ], "Tags": [ { @@ -231,13 +232,13 @@ } ], "AssumeRolePolicyDocument": { - "Version": "2012-10-17", + "Version": "2012-10-17", "Statement": [ { "Action": [ "sts:AssumeRole" - ], - "Effect": "Allow", + ], + "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com" @@ -247,33 +248,33 @@ ] } } - }, + }, "MyAwesomeFunction": { - "Type": "AWS::Lambda::Function", + "Type": "AWS::Lambda::Function", "Condition": "MyCondition", "Properties": { "Code": { - "S3Bucket": "sam-demo-bucket", + "S3Bucket": "sam-demo-bucket", "S3Key": "hello.zip" - }, - "Handler": "hello.handler", + }, + "Handler": "hello.handler", "Role": { "Fn::GetAtt": [ - "MyAwesomeFunctionRole", + "MyAwesomeFunctionRole", "Arn" ] - }, - "Runtime": "python2.7", + }, + "Runtime": "python2.7", "Tags": [ { - "Value": "SAM", + "Value": "SAM", "Key": "lambda:createdBy" } ] } }, "MyAwesomeFunctionCWEvent": { - "Type": "AWS::Events::Rule", + "Type": "AWS::Events::Rule", "Condition": "MyCondition", "Properties": { "EventPattern": { @@ -282,7 +283,7 @@ "terminated" ] } - }, + }, "Targets": [ { "Id": "MyAwesomeFunctionCWEventLambdaTarget", @@ -315,28 +316,28 @@ } }, "MyAwesomeFunctionVersion640128d35d": { - "DeletionPolicy": "Retain", - "Type": "AWS::Lambda::Version", + "DeletionPolicy": "Retain", + "Type": "AWS::Lambda::Version", "Condition": "MyCondition", "Properties": { "FunctionName": { "Ref": "MyAwesomeFunction" } } - }, + }, "Notifications": { "Condition": "MyCondition", "Type": "AWS::SNS::Topic" }, "MyAwesomeFunctionEBSchedulePermission": { - "Type": "AWS::Lambda::Permission", + "Type": "AWS::Lambda::Permission", "Condition": "MyCondition", "Properties": { - "Action": "lambda:InvokeFunction", - "Principal": "events.amazonaws.com", + "Action": "lambda:InvokeFunction", + "Principal": "events.amazonaws.com", "FunctionName": { "Ref": "MyAwesomeFunctionAliasLive" - }, + }, "SourceArn": { "Fn::GetAtt": [ "MyAwesomeFunctionEBSchedule", @@ -344,7 +345,7 @@ ] } } - }, + }, "FunctionOneImageBucketPermission": { "Type": "AWS::Lambda::Permission", "Properties": { @@ -412,7 +413,7 @@ } }, "Images": { - "Type": "AWS::S3::Bucket", + "Type": "AWS::S3::Bucket", "Properties": { "NotificationConfiguration": { "LambdaConfigurations": [ @@ -455,10 +456,152 @@ } } ] - }, + }, "DependsOn": [ "FunctionOneImageBucketPermission" ] + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "Queue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionSNSTopicWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "Queue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue": { + "Type": "AWS::SQS::Queue", + "Properties": {} + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionEventSourceMapping": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "BatchSize": 10, + "Enabled": true, + "FunctionName": { + "Ref": "MyAwesomeFunctionAliasLive" + }, + "EventSourceArn": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscription": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Endpoint": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Protocol": "sqs", + "TopicArn": { + "Ref": "Notifications" + } + }, + "Condition": "MyCondition" + }, + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueuePolicy": { + "Type": "AWS::SQS::QueuePolicy", + "Properties": { + "Queues": [ + { + "Ref": "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue" + } + ], + "PolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "sqs:SendMessage", + "Resource": { + "Fn::GetAtt": [ + "MyAwesomeFunctionAnotherSNSWithSQSSubscriptionQueue", + "Arn" + ] + }, + "Effect": "Allow", + "Condition": { + "ArnEquals": { + "aws:SourceArn": { + "Ref": "Notifications" + } + } + }, + "Principal": "*" + } + ] + } + }, + "Condition": "MyCondition" + }, + "Queue": { + "Type": "AWS::SQS::Queue", + "Condition": "MyCondition" } } } From 48cf143e85082aec1ae3fe769adff352d3f506f4 Mon Sep 17 00:00:00 2001 From: _sam <3804518+aahung@users.noreply.github.com> Date: Fri, 11 Dec 2020 09:03:58 -0800 Subject: [PATCH 09/31] chore: Remove biased language from pylintrc (#1847) --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index e4ccaec3fe..685ee70538 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,7 +7,7 @@ # pygtk.require(). #init-hook= -# Add files or directories to the blacklist. They should be base names, not +# Add files or directories to the ignore list. They should be base names, not # paths. ignore=compat.py From 04c585c1626a3ee00d5a29e2967af4c9e35da645 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Thu, 17 Dec 2020 10:56:39 -0800 Subject: [PATCH 10/31] feat: Stream Analytics (#1865) --- samtranslator/model/eventsources/pull.py | 2 ++ samtranslator/model/lambda_.py | 1 + .../translator/input/function_with_event_source_mapping.yaml | 1 + .../output/aws-cn/function_with_event_source_mapping.json | 3 ++- .../output/aws-us-gov/function_with_event_source_mapping.json | 3 ++- .../translator/output/function_with_event_source_mapping.json | 3 ++- versions/2016-10-31.md | 4 ++++ 7 files changed, 14 insertions(+), 3 deletions(-) diff --git a/samtranslator/model/eventsources/pull.py b/samtranslator/model/eventsources/pull.py index 7ead575691..2739d2196c 100644 --- a/samtranslator/model/eventsources/pull.py +++ b/samtranslator/model/eventsources/pull.py @@ -34,6 +34,7 @@ class PullEventSource(ResourceMacro): "Broker": PropertyType(False, is_str()), "Queues": PropertyType(False, is_type(list)), "SourceAccessConfigurations": PropertyType(False, is_type(list)), + "TumblingWindowInSeconds": PropertyType(False, is_type(int)), } def get_policy_arn(self): @@ -85,6 +86,7 @@ def to_cloudformation(self, **kwargs): lambda_eventsourcemapping.Topics = self.Topics lambda_eventsourcemapping.Queues = self.Queues lambda_eventsourcemapping.SourceAccessConfigurations = self.SourceAccessConfigurations + lambda_eventsourcemapping.TumblingWindowInSeconds = self.TumblingWindowInSeconds destination_config_policy = None if self.DestinationConfig: diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index 797842fb37..cd829a818c 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -75,6 +75,7 @@ class LambdaEventSourceMapping(Resource): "Topics": PropertyType(False, is_type(list)), "Queues": PropertyType(False, is_type(list)), "SourceAccessConfigurations": PropertyType(False, is_type(list)), + "TumblingWindowInSeconds": PropertyType(False, is_type(int)), } runtime_attrs = {"name": lambda self: ref(self.logical_id)} diff --git a/tests/translator/input/function_with_event_source_mapping.yaml b/tests/translator/input/function_with_event_source_mapping.yaml index 5989475caa..769cd289d8 100644 --- a/tests/translator/input/function_with_event_source_mapping.yaml +++ b/tests/translator/input/function_with_event_source_mapping.yaml @@ -52,6 +52,7 @@ Resources: BisectBatchOnFunctionError: true MaximumRecordAgeInSeconds: 86400 StartingPosition: TRIM_HORIZON + TumblingWindowInSeconds: 60 DestinationConfig: OnFailure: Type: SQS diff --git a/tests/translator/output/aws-cn/function_with_event_source_mapping.json b/tests/translator/output/aws-cn/function_with_event_source_mapping.json index b33219b62c..9b4f444784 100644 --- a/tests/translator/output/aws-cn/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-cn/function_with_event_source_mapping.json @@ -181,7 +181,8 @@ "StartingPosition": "TRIM_HORIZON", "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, - "BisectBatchOnFunctionError": true + "BisectBatchOnFunctionError": true, + "TumblingWindowInSeconds": 60 } }, "MyFunctionForBatchingExample": { diff --git a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json index 54ace4524a..3e028f48e4 100644 --- a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json @@ -181,7 +181,8 @@ "StartingPosition": "TRIM_HORIZON", "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, - "BisectBatchOnFunctionError": true + "BisectBatchOnFunctionError": true, + "TumblingWindowInSeconds": 60 } }, "MyFunctionForBatchingExample": { diff --git a/tests/translator/output/function_with_event_source_mapping.json b/tests/translator/output/function_with_event_source_mapping.json index c9d00a9c4a..c039a33968 100644 --- a/tests/translator/output/function_with_event_source_mapping.json +++ b/tests/translator/output/function_with_event_source_mapping.json @@ -181,7 +181,8 @@ "StartingPosition": "TRIM_HORIZON", "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, - "BisectBatchOnFunctionError": true + "BisectBatchOnFunctionError": true, + "TumblingWindowInSeconds": 60 } }, "MyFunctionForBatchingExample": { diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index b1aeddf637..9a009551ba 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -512,6 +512,7 @@ BisectBatchOnFunctionError | `boolean` | A boolean flag which determines whether MaximumRecordAgeInSeconds | `integer` | The maximum age of a record that will be invoked by Lambda. If an `OnFailure` destination is set, metadata describing the records will be sent to the destination. If no destination is set, the records will be bypassed DestinationConfig | [Destination Config Object](#destination-config-object) | Expired record metadata/retries and exhausted metadata is sent to this destination after they have passed the defined limits. ParallelizationFactor | `integer` | Allocates multiple virtual shards, increasing the Lambda invokes by the given factor and speeding up the stream processing. +TumblingWindowInSeconds | `integer` | Tumbling window (non-overlapping time window) duration to perform aggregations. **NOTE:** `SQSSendMessagePolicy` or `SNSPublishMessagePolicy` needs to be added in `Policies` for publishing messages to the `SQS` or `SNS` resource mentioned in `OnFailure` property @@ -534,6 +535,7 @@ Properties: OnFailure: Type: SQS Destination: !GetAtt MySqsQueue.Arn + TumblingWindowInSeconds: 0 ``` @@ -579,6 +581,7 @@ BisectBatchOnFunctionError | `boolean` | A boolean flag which determines whether MaximumRecordAgeInSeconds | `integer` | The maximum age of a record that will be invoked by Lambda. If an `OnFailure` destination is set, metadata describing the records will be sent to the destination. If no destination is set, the records will be bypassed DestinationConfig | [DestinationConfig Object](#destination-config-object) | Expired record metadata/retries and exhausted metadata is sent to this destination after they have passed the defined limits. ParallelizationFactor | `integer` | Allocates multiple virtual shards, increasing the Lambda invokes by the given factor and speeding up the stream processing. +TumblingWindowInSeconds | `integer` | Tumbling window (non-overlapping time window) duration to perform aggregations. ##### Example: DynamoDB event source object @@ -598,6 +601,7 @@ Properties: OnFailure: Type: SQS Destination: !GetAtt MySqsQueue.Arn + TumblingWindowInSeconds: 0 ``` #### SQS From 9f267a4c6f7d3ab859f88bb295f5ad60533722a2 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Thu, 17 Dec 2020 10:57:13 -0800 Subject: [PATCH 11/31] chore: version bump 1.33 (#1866) --- samtranslator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 8b5ae55d95..f31dd04782 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.32.0" +__version__ = "1.33.0" From f6e370a1af6c3241bd624edb470bd2a8641bb5f3 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Tue, 29 Dec 2020 09:42:30 -0600 Subject: [PATCH 12/31] fix: Support new CodeDeploy ManagedPolicy (#1858) * fix: Support new CodeDeploy MangedPolicy in regions without AWSCodeDeployRoleForLambda CodeDeploy is migrating from AWSCodeDeployRoleForLambda to AWSCodeDeployRoleForLambdaLimited. Some partitions do not support AWSCodeDeployRoleForLambda and therefore we need to use the newer one in those partitions. We cannot widely update to AWSCodeDeployRoleForLambdaLimited since this can cause customer's stacks to fail unexpectedly. * Forgot to commit unit tests * Handle PR feedback Co-authored-by: Jacob Fuss --- .../deployment_preference_collection.py | 14 ++++-- samtranslator/region_configuration.py | 9 ++-- samtranslator/translator/arn_generator.py | 16 ++++-- .../test_deployment_preference_collection.py | 49 +++++++++++++++++++ tests/unit/test_region_configuration.py | 38 ++++++++++++++ tests/unit/translator/test_arn_generator.py | 35 +++++++++++++ 6 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 tests/unit/model/preferences/test_deployment_preference_collection.py create mode 100644 tests/unit/test_region_configuration.py create mode 100644 tests/unit/translator/test_arn_generator.py diff --git a/samtranslator/model/preferences/deployment_preference_collection.py b/samtranslator/model/preferences/deployment_preference_collection.py index d259658bba..a226eb766e 100644 --- a/samtranslator/model/preferences/deployment_preference_collection.py +++ b/samtranslator/model/preferences/deployment_preference_collection.py @@ -101,9 +101,17 @@ def _codedeploy_iam_role(self): } ], } - iam_role.ManagedPolicyArns = [ - ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambda") - ] + + # CodeDeploy has a new managed policy. We cannot update any existing partitions, without customer reach out + # that support AWSCodeDeployRoleForLambda since this could regress stacks that are currently deployed. + if ArnGenerator.get_partition_name() in ["aws-iso", "aws-iso-b"]: + iam_role.ManagedPolicyArns = [ + ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambdaLimited") + ] + else: + iam_role.ManagedPolicyArns = [ + ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSCodeDeployRoleForLambda") + ] return iam_role diff --git a/samtranslator/region_configuration.py b/samtranslator/region_configuration.py index e8f7da533e..712d4faf9f 100644 --- a/samtranslator/region_configuration.py +++ b/samtranslator/region_configuration.py @@ -7,8 +7,6 @@ class RegionConfiguration(object): class abstracts all region/partition specific configuration. """ - partitions = {"govcloud": "aws-us-gov", "china": "aws-cn"} - @classmethod def is_apigw_edge_configuration_supported(cls): """ @@ -18,4 +16,9 @@ def is_apigw_edge_configuration_supported(cls): :return: True, if API Gateway does not support Edge configuration """ - return ArnGenerator.get_partition_name() not in [cls.partitions["govcloud"], cls.partitions["china"]] + return ArnGenerator.get_partition_name() not in [ + "aws-us-gov", + "aws-iso", + "aws-iso-b", + "aws-cn", + ] diff --git a/samtranslator/translator/arn_generator.py b/samtranslator/translator/arn_generator.py index 633a7ba7a6..b325c368e9 100644 --- a/samtranslator/translator/arn_generator.py +++ b/samtranslator/translator/arn_generator.py @@ -39,15 +39,23 @@ def get_partition_name(cls, region=None): :param region: Optional name of the region :return: Partition name """ + if region is None: # Use Boto3 to get the region where code is running. This uses Boto's regular region resolution # mechanism, starting from AWS_DEFAULT_REGION environment variable. region = boto3.session.Session().region_name + # setting default partition to aws, this will be overwritten by checking the region below + partition = "aws" + region_string = region.lower() if region_string.startswith("cn-"): - return "aws-cn" + partition = "aws-cn" + elif region_string.startswith("us-iso-"): + partition = "aws-iso" + elif region_string.startswith("us-isob"): + partition = "aws-iso-b" elif region_string.startswith("us-gov"): - return "aws-us-gov" - else: - return "aws" + partition = "aws-us-gov" + + return partition diff --git a/tests/unit/model/preferences/test_deployment_preference_collection.py b/tests/unit/model/preferences/test_deployment_preference_collection.py new file mode 100644 index 0000000000..3412b9f5c7 --- /dev/null +++ b/tests/unit/model/preferences/test_deployment_preference_collection.py @@ -0,0 +1,49 @@ +from unittest import TestCase + +from mock import patch +from parameterized import parameterized + +from samtranslator.model.preferences.deployment_preference_collection import DeploymentPreferenceCollection + + +class TestDeploymentPreferenceCollection(TestCase): + @parameterized.expand( + [ + ["aws-iso"], + ["aws-iso-b"], + ] + ) + def test_codedeploy_iam_role_contains_AWSCodeDeployRoleForLambdaLimited_managedpolicy(self, partition): + + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + iam_role = DeploymentPreferenceCollection().codedeploy_iam_role + + self.assertIn( + "arn:{}:iam::aws:policy/service-role/AWSCodeDeployRoleForLambdaLimited".format(partition), + iam_role.ManagedPolicyArns, + ) + + @parameterized.expand( + [ + ["aws"], + ["aws-cn"], + ["aws-us-gov"], + ] + ) + def test_codedeploy_iam_role_contains_AWSCodeDeployRoleForLambda_managedpolicy(self, partition): + + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + iam_role = DeploymentPreferenceCollection().codedeploy_iam_role + + self.assertIn( + "arn:{}:iam::aws:policy/service-role/AWSCodeDeployRoleForLambda".format(partition), + iam_role.ManagedPolicyArns, + ) diff --git a/tests/unit/test_region_configuration.py b/tests/unit/test_region_configuration.py new file mode 100644 index 0000000000..646b0c1361 --- /dev/null +++ b/tests/unit/test_region_configuration.py @@ -0,0 +1,38 @@ +from unittest import TestCase + +from mock import patch +from parameterized import parameterized + +from samtranslator.region_configuration import RegionConfiguration + + +class TestRegionConfiguration(TestCase): + @parameterized.expand( + [ + ["aws"], + ] + ) + def test_when_apigw_edge_configuration_supported(self, partition): + + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + self.assertTrue(RegionConfiguration.is_apigw_edge_configuration_supported()) + + @parameterized.expand( + [ + ["aws-cn"], + ["aws-us-gov"], + ["aws-iso"], + ["aws-iso-b"], + ] + ) + def test_when_apigw_edge_configuration_is_not_supported(self, partition): + with patch( + "samtranslator.translator.arn_generator.ArnGenerator.get_partition_name" + ) as get_partition_name_patch: + get_partition_name_patch.return_value = partition + + self.assertFalse(RegionConfiguration.is_apigw_edge_configuration_supported()) diff --git a/tests/unit/translator/test_arn_generator.py b/tests/unit/translator/test_arn_generator.py new file mode 100644 index 0000000000..3d0d330161 --- /dev/null +++ b/tests/unit/translator/test_arn_generator.py @@ -0,0 +1,35 @@ +from unittest import TestCase + +from mock import patch +from parameterized import parameterized + +from samtranslator.translator.arn_generator import ArnGenerator + + +class TestArnGenerator(TestCase): + @parameterized.expand( + [ + ["us-east-1", "aws"], + ["eu-west-1", "aws"], + ["cn-north-1", "aws-cn"], + ["us-gov-west-1", "aws-us-gov"], + ["us-iso-east-1", "aws-iso"], + ["us-isob-east-1", "aws-iso-b"], + ] + ) + def test_get_partition_name(self, region, expected_partition): + self.assertEqual(expected_partition, ArnGenerator.get_partition_name(region=region)) + + @parameterized.expand( + [ + ["us-east-1", "aws"], + ["eu-west-1", "aws"], + ["cn-north-1", "aws-cn"], + ["us-gov-west-1", "aws-us-gov"], + ["us-iso-east-1", "aws-iso"], + ["us-isob-east-1", "aws-iso-b"], + ] + ) + def test_get_partition_name_when_region_not_provided(self, region, expected_partition): + with patch("boto3.session.Session.region_name", region): + self.assertEqual(expected_partition, ArnGenerator.get_partition_name()) From fd840a3c3537a02d8c8a1a5b60acaefa6a41ee45 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Thu, 31 Dec 2020 04:14:11 -0600 Subject: [PATCH 13/31] fix: Update Slack invite link (#1877) Co-authored-by: Jacob Fuss --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 244a2ed2ba..0b5f186bdf 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Read the [SAM Documentation Contribution Guide](https://github.com/awsdocs/aws-s started. ### Join the SAM Community on Slack -[Join the SAM developers channel (#samdev)](https://join.slack.com/t/awsdevelopers/shared_invite/zt-h82odes6-qYN2Cxit7hBGIvC6oMjGpg) on Slack to collaborate with fellow community members and the AWS SAM team. +[Join the SAM developers channel (#samdev)](https://join.slack.com/t/awsdevelopers/shared_invite/zt-idww18e8-Z1kXhI7GNuDewkweCF3YjA) on Slack to collaborate with fellow community members and the AWS SAM team. From 3e2edecb80bd75b0b002561a9390f08414102259 Mon Sep 17 00:00:00 2001 From: vinayaksood Date: Thu, 7 Jan 2021 15:14:54 -0800 Subject: [PATCH 14/31] feature: Support for custom checkpointing (#1883) Co-authored-by: Vinayak --- samtranslator/model/eventsources/pull.py | 2 ++ samtranslator/model/lambda_.py | 1 + .../input/function_with_event_source_mapping.yaml | 2 ++ .../output/aws-cn/function_with_event_source_mapping.json | 5 +++-- .../aws-us-gov/function_with_event_source_mapping.json | 5 +++-- .../output/function_with_event_source_mapping.json | 5 +++-- versions/2016-10-31.md | 6 ++++++ 7 files changed, 20 insertions(+), 6 deletions(-) diff --git a/samtranslator/model/eventsources/pull.py b/samtranslator/model/eventsources/pull.py index 2739d2196c..95afc5f3ab 100644 --- a/samtranslator/model/eventsources/pull.py +++ b/samtranslator/model/eventsources/pull.py @@ -35,6 +35,7 @@ class PullEventSource(ResourceMacro): "Queues": PropertyType(False, is_type(list)), "SourceAccessConfigurations": PropertyType(False, is_type(list)), "TumblingWindowInSeconds": PropertyType(False, is_type(int)), + "FunctionResponseTypes": PropertyType(False, is_type(list)), } def get_policy_arn(self): @@ -87,6 +88,7 @@ def to_cloudformation(self, **kwargs): lambda_eventsourcemapping.Queues = self.Queues lambda_eventsourcemapping.SourceAccessConfigurations = self.SourceAccessConfigurations lambda_eventsourcemapping.TumblingWindowInSeconds = self.TumblingWindowInSeconds + lambda_eventsourcemapping.FunctionResponseTypes = self.FunctionResponseTypes destination_config_policy = None if self.DestinationConfig: diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index cd829a818c..c473bbd6fa 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -76,6 +76,7 @@ class LambdaEventSourceMapping(Resource): "Queues": PropertyType(False, is_type(list)), "SourceAccessConfigurations": PropertyType(False, is_type(list)), "TumblingWindowInSeconds": PropertyType(False, is_type(int)), + "FunctionResponseTypes": PropertyType(False, is_type(list)), } runtime_attrs = {"name": lambda self: ref(self.logical_id)} diff --git a/tests/translator/input/function_with_event_source_mapping.yaml b/tests/translator/input/function_with_event_source_mapping.yaml index 769cd289d8..edea3e214f 100644 --- a/tests/translator/input/function_with_event_source_mapping.yaml +++ b/tests/translator/input/function_with_event_source_mapping.yaml @@ -53,6 +53,8 @@ Resources: MaximumRecordAgeInSeconds: 86400 StartingPosition: TRIM_HORIZON TumblingWindowInSeconds: 60 + FunctionResponseTypes: + - ReportBatchItemFailures DestinationConfig: OnFailure: Type: SQS diff --git a/tests/translator/output/aws-cn/function_with_event_source_mapping.json b/tests/translator/output/aws-cn/function_with_event_source_mapping.json index 9b4f444784..c566b9490f 100644 --- a/tests/translator/output/aws-cn/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-cn/function_with_event_source_mapping.json @@ -182,7 +182,8 @@ "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, "BisectBatchOnFunctionError": true, - "TumblingWindowInSeconds": 60 + "TumblingWindowInSeconds": 60, + "FunctionResponseTypes": ["ReportBatchItemFailures"] } }, "MyFunctionForBatchingExample": { @@ -239,4 +240,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json index 3e028f48e4..a910aefedc 100644 --- a/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json +++ b/tests/translator/output/aws-us-gov/function_with_event_source_mapping.json @@ -182,7 +182,8 @@ "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, "BisectBatchOnFunctionError": true, - "TumblingWindowInSeconds": 60 + "TumblingWindowInSeconds": 60, + "FunctionResponseTypes": ["ReportBatchItemFailures"] } }, "MyFunctionForBatchingExample": { @@ -239,4 +240,4 @@ } } } -} \ No newline at end of file +} diff --git a/tests/translator/output/function_with_event_source_mapping.json b/tests/translator/output/function_with_event_source_mapping.json index c039a33968..2a2f668efc 100644 --- a/tests/translator/output/function_with_event_source_mapping.json +++ b/tests/translator/output/function_with_event_source_mapping.json @@ -182,7 +182,8 @@ "ParallelizationFactor": 8, "MaximumRetryAttempts": 100, "BisectBatchOnFunctionError": true, - "TumblingWindowInSeconds": 60 + "TumblingWindowInSeconds": 60, + "FunctionResponseTypes": ["ReportBatchItemFailures"] } }, "MyFunctionForBatchingExample": { @@ -239,4 +240,4 @@ } } } -} \ No newline at end of file +} diff --git a/versions/2016-10-31.md b/versions/2016-10-31.md index 9a009551ba..3b5f185934 100644 --- a/versions/2016-10-31.md +++ b/versions/2016-10-31.md @@ -513,6 +513,7 @@ MaximumRecordAgeInSeconds | `integer` | The maximum age of a record that will be DestinationConfig | [Destination Config Object](#destination-config-object) | Expired record metadata/retries and exhausted metadata is sent to this destination after they have passed the defined limits. ParallelizationFactor | `integer` | Allocates multiple virtual shards, increasing the Lambda invokes by the given factor and speeding up the stream processing. TumblingWindowInSeconds | `integer` | Tumbling window (non-overlapping time window) duration to perform aggregations. +FunctionResponseTypes | `list` | Response types enabled for your function. **NOTE:** `SQSSendMessagePolicy` or `SNSPublishMessagePolicy` needs to be added in `Policies` for publishing messages to the `SQS` or `SNS` resource mentioned in `OnFailure` property @@ -536,6 +537,8 @@ Properties: Type: SQS Destination: !GetAtt MySqsQueue.Arn TumblingWindowInSeconds: 0 + FunctionResponseTypes: + - ReportBatchItemFailures ``` @@ -582,6 +585,7 @@ MaximumRecordAgeInSeconds | `integer` | The maximum age of a record that will be DestinationConfig | [DestinationConfig Object](#destination-config-object) | Expired record metadata/retries and exhausted metadata is sent to this destination after they have passed the defined limits. ParallelizationFactor | `integer` | Allocates multiple virtual shards, increasing the Lambda invokes by the given factor and speeding up the stream processing. TumblingWindowInSeconds | `integer` | Tumbling window (non-overlapping time window) duration to perform aggregations. +FunctionResponseTypes | `list` | Response types enabled for your function. ##### Example: DynamoDB event source object @@ -602,6 +606,8 @@ Properties: Type: SQS Destination: !GetAtt MySqsQueue.Arn TumblingWindowInSeconds: 0 + FunctionResponseTypes + - ReportBatchItemFailures ``` #### SQS From 5658413aaea209927f3ae4f55093717ff1e45540 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Fri, 8 Jan 2021 09:51:42 -0800 Subject: [PATCH 15/31] Fix: Description in AWS::Serverless::HttpApi (#1884) * Fix: Description in AWS::Serverless::HttpApi * Update _set to _add --- samtranslator/model/api/http_api_generator.py | 26 ++++++++-- samtranslator/open_api/open_api.py | 13 +++++ tests/model/test_sam_resources.py | 50 ++++++++++++++++--- tests/openapi/test_openapi.py | 23 +++++++++ .../input/http_api_description.yaml | 5 +- .../output/aws-cn/http_api_description.json | 37 ++++++++++++-- .../aws-us-gov/http_api_description.json | 37 ++++++++++++-- .../output/http_api_description.json | 37 ++++++++++++-- 8 files changed, 202 insertions(+), 26 deletions(-) diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index 2c57d86a58..d9e07d5d67 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -112,6 +112,8 @@ def _construct_http_api(self): if self.disable_execute_api_endpoint is not None: self._add_endpoint_configuration() + self._add_description() + if self.definition_uri: http_api.BodyS3Location = self._construct_body_s3_dict() elif self.definition_body: @@ -124,9 +126,6 @@ def _construct_http_api(self): "add a 'HttpApi' event to an 'AWS::Serverless::Function'.", ) - if self.description: - http_api.Description = self.description - return http_api def _add_endpoint_configuration(self): @@ -586,6 +585,27 @@ def _construct_stage(self): return stage + def _add_description(self): + """Add description to DefinitionBody if Description property is set in SAM""" + if not self.description: + return + + if not self.definition_body: + raise InvalidResourceException( + self.logical_id, + "Description works only with inline OpenApi specified in the 'DefinitionBody' property.", + ) + if self.definition_body.get("info", {}).get("description"): + raise InvalidResourceException( + self.logical_id, + "Unable to set Description because it is already defined within inline OpenAPI specified in the " + "'DefinitionBody' property.", + ) + + open_api_editor = OpenApiEditor(self.definition_body) + open_api_editor.add_description(self.description) + self.definition_body = open_api_editor.openapi + def to_cloudformation(self): """Generates CloudFormation resources from a SAM HTTP API resource diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index 8dea2aa520..b58801073d 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -46,6 +46,7 @@ def __init__(self, doc): self.security_schemes = self._doc.get("components", {}).get("securitySchemes", {}) self.definitions = self._doc.get("definitions", {}) self.tags = self._doc.get("tags", []) + self.info = self._doc.get("info", {}) def get_path(self, path): """ @@ -528,6 +529,15 @@ def add_cors( self._doc[self._X_APIGW_CORS] = cors_configuration + def add_description(self, description): + """Add description in open api definition, if it is not already defined + + :param string description: Description of the API + """ + if self.info.get("description"): + return + self.info["description"] = description + def has_api_gateway_cors(self): if self._doc.get(self._X_APIGW_CORS): return True @@ -551,6 +561,9 @@ def openapi(self): self._doc.setdefault("components", {}) self._doc["components"]["securitySchemes"] = self.security_schemes + if self.info: + self._doc["info"] = self.info + return copy.deepcopy(self._doc) @staticmethod diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index bd6a6789d4..e84c5b1ce3 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -281,18 +281,54 @@ class TestHttpApiDescription(TestCase): @patch("boto3.session.Session.region_name", "eu-central-1") def test_with_no_description(self): sam_http_api = SamHttpApi("foo") - sam_http_api.DefinitionUri = "s3://foobar/foo.zip" + sam_http_api.DefinitionBody = { + "openapi": "3.0.1", + "paths": {"/foo": {}, "/bar": {}}, + "info": {"description": "existing description"}, + } resources = sam_http_api.to_cloudformation(**self.kwargs) - rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] - self.assertEqual(rest_api[0].Description, None) + http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] + self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "existing description") @patch("boto3.session.Session.region_name", "eu-central-1") - def test_with_description(self): + def test_with_no_definition_body(self): sam_http_api = SamHttpApi("foo") - sam_http_api.DefinitionUri = "s3://foobar/foo.zip" sam_http_api.Description = "my description" + with self.assertRaises(InvalidResourceException) as context: + sam_http_api.to_cloudformation(**self.kwargs) + self.assertEqual( + context.exception.message, + "Resource with id [foo] is invalid. " + "Description works only with inline OpenApi specified in the 'DefinitionBody' property.", + ) + + @patch("boto3.session.Session.region_name", "eu-central-1") + def test_with_description_defined_in_definition_body(self): + sam_http_api = SamHttpApi("foo") + sam_http_api.DefinitionBody = { + "openapi": "3.0.1", + "paths": {"/foo": {}, "/bar": {}}, + "info": {"description": "existing description"}, + } + sam_http_api.Description = "new description" + + with self.assertRaises(InvalidResourceException) as context: + sam_http_api.to_cloudformation(**self.kwargs) + self.assertEqual( + context.exception.message, + "Resource with id [foo] is invalid. " + "Unable to set Description because it is already defined within inline OpenAPI specified in the " + "'DefinitionBody' property.", + ) + + @patch("boto3.session.Session.region_name", "eu-central-1") + def test_with_description_not_defined_in_definition_body(self): + sam_http_api = SamHttpApi("foo") + sam_http_api.DefinitionBody = {"openapi": "3.0.1", "paths": {"/foo": {}}, "info": {}} + sam_http_api.Description = "new description" + resources = sam_http_api.to_cloudformation(**self.kwargs) - rest_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] - self.assertEqual(rest_api[0].Description, "my description") + http_api = [x for x in resources if isinstance(x, ApiGatewayV2HttpApi)] + self.assertEqual(http_api[0].Body.get("info", {}).get("description"), "new description") diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index a70227e358..9938cab1c7 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -414,3 +414,26 @@ def test_must_get_integration_function_if_exists(self): "HttpApiFunction", ) self.assertFalse(self.editor.get_integration_function_logical_id("/bar", "get")) + + +class TestOpenApiEdit_add_description(TestCase): + def setUp(self): + self.original_openapi_with_description = { + "openapi": "3.0.1", + "paths": {}, + "info": {"description": "Existing Description"}, + } + self.original_openapi_without_description = { + "openapi": "3.0.1", + "paths": {}, + } + + def test_must_add_description_if_not_defined(self): + editor = OpenApiEditor(self.original_openapi_without_description) + editor.add_description("New Description") + self.assertEqual(editor.openapi["info"]["description"], "New Description") + + def test_must_not_add_description_if_already_defined(self): + editor = OpenApiEditor(self.original_openapi_with_description) + editor.add_description("New Description") + self.assertEqual(editor.openapi["info"]["description"], "Existing Description") diff --git a/tests/translator/input/http_api_description.yaml b/tests/translator/input/http_api_description.yaml index 6dec788ba7..268584d89d 100644 --- a/tests/translator/input/http_api_description.yaml +++ b/tests/translator/input/http_api_description.yaml @@ -2,7 +2,10 @@ Resources: HttpApi: Type: AWS::Serverless::HttpApi Properties: - DefinitionUri: s3://bucket/key + DefinitionBody: + openapi: "3.0.1" + paths: + "/foo": {} Description: my description Function: diff --git a/tests/translator/output/aws-cn/http_api_description.json b/tests/translator/output/aws-cn/http_api_description.json index 0798fc4e2a..e5b49fcb3e 100644 --- a/tests/translator/output/aws-cn/http_api_description.json +++ b/tests/translator/output/aws-cn/http_api_description.json @@ -79,17 +79,44 @@ "ApiId": { "Ref": "HttpApi" }, + "Tags": { + "httpapi:createdBy": "SAM" + }, "StageName": "$default" } }, "HttpApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { - "BodyS3Location": { - "Bucket": "bucket", - "Key": "key" - }, - "Description": "my description" + "Body": { + "openapi": "3.0.1", + "paths": { + "/foo": {}, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "info": { + "description": "my description" + } + } } } } diff --git a/tests/translator/output/aws-us-gov/http_api_description.json b/tests/translator/output/aws-us-gov/http_api_description.json index c7594a0ec6..dd4a7f36c9 100644 --- a/tests/translator/output/aws-us-gov/http_api_description.json +++ b/tests/translator/output/aws-us-gov/http_api_description.json @@ -79,17 +79,44 @@ "ApiId": { "Ref": "HttpApi" }, + "Tags": { + "httpapi:createdBy": "SAM" + }, "StageName": "$default" } }, "HttpApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { - "BodyS3Location": { - "Bucket": "bucket", - "Key": "key" - }, - "Description": "my description" + "Body": { + "openapi": "3.0.1", + "paths": { + "/foo": {}, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "info": { + "description": "my description" + } + } } } } diff --git a/tests/translator/output/http_api_description.json b/tests/translator/output/http_api_description.json index 682ece8dde..fed22b6856 100644 --- a/tests/translator/output/http_api_description.json +++ b/tests/translator/output/http_api_description.json @@ -79,17 +79,44 @@ "ApiId": { "Ref": "HttpApi" }, + "Tags": { + "httpapi:createdBy": "SAM" + }, "StageName": "$default" } }, "HttpApi": { "Type": "AWS::ApiGatewayV2::Api", "Properties": { - "BodyS3Location": { - "Bucket": "bucket", - "Key": "key" - }, - "Description": "my description" + "Body": { + "openapi": "3.0.1", + "paths": { + "/foo": {}, + "$default": { + "x-amazon-apigateway-any-method": { + "x-amazon-apigateway-integration": { + "type": "aws_proxy", + "httpMethod": "POST", + "payloadFormatVersion": "2.0", + "uri": { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + } + }, + "isDefaultRoute": true, + "responses": {} + } + } + }, + "tags": [ + { + "name": "httpapi:createdBy", + "x-amazon-apigateway-tag-value": "SAM" + } + ], + "info": { + "description": "my description" + } + } } } } From 75230ba469c6c7edf77c3fdeeab178c72a203b26 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Fri, 8 Jan 2021 13:03:13 -0800 Subject: [PATCH 16/31] Update AWS::S3::Bucket properties (#1885) * Update AWS::S3::Bucket properties * Fix type checking validators for AWS::S3::Bucket * Update to use any_type() in favor of supporing ref --- samtranslator/model/s3.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/samtranslator/model/s3.py b/samtranslator/model/s3.py index 6c60c26c78..57325e9004 100644 --- a/samtranslator/model/s3.py +++ b/samtranslator/model/s3.py @@ -12,11 +12,15 @@ class S3Bucket(Resource): "BucketEncryption": PropertyType(False, any_type()), "BucketName": PropertyType(False, is_str()), "CorsConfiguration": PropertyType(False, any_type()), + "IntelligentTieringConfigurations": PropertyType(False, any_type()), "InventoryConfigurations": PropertyType(False, any_type()), "LifecycleConfiguration": PropertyType(False, any_type()), "LoggingConfiguration": PropertyType(False, any_type()), "MetricsConfigurations": PropertyType(False, any_type()), "NotificationConfiguration": PropertyType(False, is_type(dict)), + "ObjectLockConfiguration": PropertyType(False, any_type()), + "ObjectLockEnabled": PropertyType(False, any_type()), + "OwnershipControls": PropertyType(False, any_type()), "PublicAccessBlockConfiguration": PropertyType(False, is_type(dict)), "ReplicationConfiguration": PropertyType(False, any_type()), "Tags": PropertyType(False, is_type(list)), From fe838d78795dc902cb04bfb884cd306b97c3d10e Mon Sep 17 00:00:00 2001 From: Qingchuan Ma <69653965+qingchm@users.noreply.github.com> Date: Thu, 14 Jan 2021 15:45:54 -0800 Subject: [PATCH 17/31] Fix: Replaced invalid AMQ managed policy by providing policy statements (#1891) * Fix for invalid MQ event source managed policy * Fix for invalid managed policy for MQ, included support for new MQ event source property, updated test cases * Black reformatting * Test case changes * Changed policy name * Modified test cases with new policy name --- samtranslator/model/eventsources/pull.py | 86 +++++++++++++- .../{amq.yaml => function_with_amq.yaml} | 0 .../input/function_with_amq_kms.yaml | 18 +++ tests/translator/output/amq.json | 70 ------------ tests/translator/output/aws-cn/amq.json | 70 ------------ .../output/aws-cn/function_with_amq.json | 98 ++++++++++++++++ .../output/aws-cn/function_with_amq_kms.json | 105 ++++++++++++++++++ tests/translator/output/aws-us-gov/amq.json | 70 ------------ .../output/aws-us-gov/function_with_amq.json | 98 ++++++++++++++++ .../aws-us-gov/function_with_amq_kms.json | 105 ++++++++++++++++++ .../translator/output/function_with_amq.json | 98 ++++++++++++++++ .../output/function_with_amq_kms.json | 105 ++++++++++++++++++ tests/translator/test_translator.py | 3 +- 13 files changed, 712 insertions(+), 214 deletions(-) rename tests/translator/input/{amq.yaml => function_with_amq.yaml} (100%) create mode 100644 tests/translator/input/function_with_amq_kms.yaml delete mode 100644 tests/translator/output/amq.json delete mode 100644 tests/translator/output/aws-cn/amq.json create mode 100644 tests/translator/output/aws-cn/function_with_amq.json create mode 100644 tests/translator/output/aws-cn/function_with_amq_kms.json delete mode 100644 tests/translator/output/aws-us-gov/amq.json create mode 100644 tests/translator/output/aws-us-gov/function_with_amq.json create mode 100644 tests/translator/output/aws-us-gov/function_with_amq_kms.json create mode 100644 tests/translator/output/function_with_amq.json create mode 100644 tests/translator/output/function_with_amq_kms.json diff --git a/samtranslator/model/eventsources/pull.py b/samtranslator/model/eventsources/pull.py index 95afc5f3ab..747968bb96 100644 --- a/samtranslator/model/eventsources/pull.py +++ b/samtranslator/model/eventsources/pull.py @@ -34,6 +34,7 @@ class PullEventSource(ResourceMacro): "Broker": PropertyType(False, is_str()), "Queues": PropertyType(False, is_type(list)), "SourceAccessConfigurations": PropertyType(False, is_type(list)), + "SecretsManagerKmsKeyId": PropertyType(False, is_str()), "TumblingWindowInSeconds": PropertyType(False, is_type(int)), "FunctionResponseTypes": PropertyType(False, is_type(list)), } @@ -41,6 +42,9 @@ class PullEventSource(ResourceMacro): def get_policy_arn(self): raise NotImplementedError("Subclass must implement this method") + def get_policy_statements(self): + raise NotImplementedError("Subclass must implement this method") + def to_cloudformation(self, **kwargs): """Returns the Lambda EventSourceMapping to which this pull event corresponds. Adds the appropriate managed policy to the function's execution role, if such a role is provided. @@ -133,8 +137,17 @@ def _link_policy(self, role, destination_config_policy=None): :param model.iam.IAMRole role: the execution role generated for the function """ policy_arn = self.get_policy_arn() - if role is not None and policy_arn not in role.ManagedPolicyArns: - role.ManagedPolicyArns.append(policy_arn) + policy_statements = self.get_policy_statements() + if role is not None: + if policy_arn is not None and policy_arn not in role.ManagedPolicyArns: + role.ManagedPolicyArns.append(policy_arn) + if policy_statements is not None: + if role.Policies is None: + role.Policies = [] + for policy in policy_statements: + if policy not in role.Policies: + if not policy.get("PolicyDocument") in [d["PolicyDocument"] for d in role.Policies]: + role.Policies.append(policy) # add SQS or SNS policy only if role is present in kwargs if role is not None and destination_config_policy is not None and destination_config_policy: if role.Policies is None: @@ -154,6 +167,9 @@ class Kinesis(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaKinesisExecutionRole") + def get_policy_statements(self): + return None + class DynamoDB(PullEventSource): """DynamoDB Streams event source.""" @@ -163,6 +179,9 @@ class DynamoDB(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaDynamoDBExecutionRole") + def get_policy_statements(self): + return None + class SQS(PullEventSource): """SQS Queue event source.""" @@ -172,6 +191,9 @@ class SQS(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaSQSQueueExecutionRole") + def get_policy_statements(self): + return None + class MSK(PullEventSource): """MSK event source.""" @@ -181,6 +203,9 @@ class MSK(PullEventSource): def get_policy_arn(self): return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaMSKExecutionRole") + def get_policy_statements(self): + return None + class MQ(PullEventSource): """MQ event source.""" @@ -188,4 +213,59 @@ class MQ(PullEventSource): resource_type = "MQ" def get_policy_arn(self): - return ArnGenerator.generate_aws_managed_policy_arn("service-role/AWSLambdaAMQExecutionRole") + return None + + def get_policy_statements(self): + if not self.SourceAccessConfigurations: + raise InvalidEventException( + self.relative_id, + "No SourceAccessConfigurations for ActiveMQ provided.", + ) + if not type(self.SourceAccessConfigurations) is list: + raise InvalidEventException( + self.relative_id, + "Provided SourceAccessConfigurations cannot be parsed into a list.", + ) + # MQ only supports SourceAccessConfigurations with list size of 1 + if not (len(self.SourceAccessConfigurations) == 1): + raise InvalidEventException( + self.relative_id, + "SourceAccessConfigurations for ActiveMQ only supports single configuration entry.", + ) + if not self.SourceAccessConfigurations[0].get("URI"): + raise InvalidEventException( + self.relative_id, + "No URI property specified in SourceAccessConfigurations for ActiveMQ.", + ) + document = { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue", + ], + "Effect": "Allow", + "Resource": self.SourceAccessConfigurations[0].get("URI"), + }, + { + "Action": [ + "mq:DescribeBroker", + ], + "Effect": "Allow", + "Resource": self.Broker, + }, + ] + }, + } + if self.SecretsManagerKmsKeyId: + kms_policy = { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/" + + self.SecretsManagerKmsKeyId + }, + } + document["PolicyDocument"]["Statement"].append(kms_policy) + return [document] diff --git a/tests/translator/input/amq.yaml b/tests/translator/input/function_with_amq.yaml similarity index 100% rename from tests/translator/input/amq.yaml rename to tests/translator/input/function_with_amq.yaml diff --git a/tests/translator/input/function_with_amq_kms.yaml b/tests/translator/input/function_with_amq_kms.yaml new file mode 100644 index 0000000000..7ea02c2dff --- /dev/null +++ b/tests/translator/input/function_with_amq_kms.yaml @@ -0,0 +1,18 @@ +Resources: + MQFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: s3://sam-demo-bucket/queues.zip + Handler: queue.mq_handler + Runtime: python2.7 + Events: + MyMQQueue: + Type: MQ + Properties: + Broker: arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9 + Queues: + - "Queue1" + SourceAccessConfigurations: + - Type: BASIC_AUTH + URI: arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c + SecretsManagerKmsKeyId: 1abc23d4-567f-8ab9-cde0-1fab234c5d67 \ No newline at end of file diff --git a/tests/translator/output/amq.json b/tests/translator/output/amq.json deleted file mode 100644 index 796785adc1..0000000000 --- a/tests/translator/output/amq.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "Resources": { - "MQFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "queues.zip" - }, - "Handler": "queue.mq_handler", - "Role": { - "Fn::GetAtt": [ - "MQFunctionRole", - "Arn" - ] - }, - "Runtime": "python2.7", - "Tags": [{ - "Value": "SAM", - "Key": "lambda:createdBy" - }] - } - }, - "MQFunctionMyMQQueue": { - "Type": "AWS::Lambda::EventSourceMapping", - "Properties": { - "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", - "FunctionName": { - "Ref": "MQFunction" - }, - "Queues": ["Queue1"], - "SourceAccessConfigurations": [ - { - "Type": "BASIC_AUTH", - "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" - } - ] - } - }, - "MQFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws:iam::aws:policy/service-role/AWSLambdaAMQExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } - }] - } - } - } - } -} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/amq.json b/tests/translator/output/aws-cn/amq.json deleted file mode 100644 index b752676634..0000000000 --- a/tests/translator/output/aws-cn/amq.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "Resources": { - "MQFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "queues.zip" - }, - "Handler": "queue.mq_handler", - "Role": { - "Fn::GetAtt": [ - "MQFunctionRole", - "Arn" - ] - }, - "Runtime": "python2.7", - "Tags": [{ - "Value": "SAM", - "Key": "lambda:createdBy" - }] - } - }, - "MQFunctionMyMQQueue": { - "Type": "AWS::Lambda::EventSourceMapping", - "Properties": { - "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", - "FunctionName": { - "Ref": "MQFunction" - }, - "Queues": ["Queue1"], - "SourceAccessConfigurations": [ - { - "Type": "BASIC_AUTH", - "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" - } - ] - } - }, - "MQFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaAMQExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } - }] - } - } - } - } -} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_amq.json b/tests/translator/output/aws-cn/function_with_amq.json new file mode 100644 index 0000000000..6a34ed7714 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_amq.json @@ -0,0 +1,98 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-cn/function_with_amq_kms.json b/tests/translator/output/aws-cn/function_with_amq_kms.json new file mode 100644 index 0000000000..e79122065e --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_amq_kms.json @@ -0,0 +1,105 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/1abc23d4-567f-8ab9-cde0-1fab234c5d67" + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/amq.json b/tests/translator/output/aws-us-gov/amq.json deleted file mode 100644 index 5a42ee959c..0000000000 --- a/tests/translator/output/aws-us-gov/amq.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "Resources": { - "MQFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "sam-demo-bucket", - "S3Key": "queues.zip" - }, - "Handler": "queue.mq_handler", - "Role": { - "Fn::GetAtt": [ - "MQFunctionRole", - "Arn" - ] - }, - "Runtime": "python2.7", - "Tags": [{ - "Value": "SAM", - "Key": "lambda:createdBy" - }] - } - }, - "MQFunctionMyMQQueue": { - "Type": "AWS::Lambda::EventSourceMapping", - "Properties": { - "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", - "FunctionName": { - "Ref": "MQFunction" - }, - "Queues": ["Queue1"], - "SourceAccessConfigurations": [ - { - "Type": "BASIC_AUTH", - "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" - } - ] - } - }, - "MQFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "ManagedPolicyArns": [ - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaAMQExecutionRole" - ], - "Tags": [ - { - "Value": "SAM", - "Key": "lambda:createdBy" - } - ], - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [{ - "Action": [ - "sts:AssumeRole" - ], - "Effect": "Allow", - "Principal": { - "Service": [ - "lambda.amazonaws.com" - ] - } - }] - } - } - } - } -} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_amq.json b/tests/translator/output/aws-us-gov/function_with_amq.json new file mode 100644 index 0000000000..a8a471c860 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_amq.json @@ -0,0 +1,98 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/aws-us-gov/function_with_amq_kms.json b/tests/translator/output/aws-us-gov/function_with_amq_kms.json new file mode 100644 index 0000000000..37b991a497 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_amq_kms.json @@ -0,0 +1,105 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/1abc23d4-567f-8ab9-cde0-1fab234c5d67" + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/output/function_with_amq.json b/tests/translator/output/function_with_amq.json new file mode 100644 index 0000000000..0d35f71c2a --- /dev/null +++ b/tests/translator/output/function_with_amq.json @@ -0,0 +1,98 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } +} \ No newline at end of file diff --git a/tests/translator/output/function_with_amq_kms.json b/tests/translator/output/function_with_amq_kms.json new file mode 100644 index 0000000000..94ae8666a3 --- /dev/null +++ b/tests/translator/output/function_with_amq_kms.json @@ -0,0 +1,105 @@ +{ + "Resources": { + "MQFunction": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "queues.zip" + }, + "Handler": "queue.mq_handler", + "Role": { + "Fn::GetAtt": [ + "MQFunctionRole", + "Arn" + ] + }, + "Runtime": "python2.7", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Policies": [ + { + "PolicyName": "SamAutoGeneratedAMQPolicy", + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + }, + { + "Action": [ + "mq:DescribeBroker" + ], + "Effect": "Allow", + "Resource": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9" + }, + { + "Action": "kms:Decrypt", + "Effect": "Allow", + "Resource": { + "Fn::Sub": "arn:${AWS::Partition}:kms:${AWS::Region}:${AWS::AccountId}:key/1abc23d4-567f-8ab9-cde0-1fab234c5d67" + } + } + ] + } + } + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + } + }, + "MQFunctionMyMQQueue": { + "Type": "AWS::Lambda::EventSourceMapping", + "Properties": { + "EventSourceArn": "arn:aws:mq:us-east-2:123456789012:broker:MyBroker:b-1234a5b6-78cd-901e-2fgh-3i45j6k178l9", + "FunctionName": { + "Ref": "MQFunction" + }, + "Queues": [ + "Queue1" + ], + "SourceAccessConfigurations": [ + { + "Type": "BASIC_AUTH", + "URI": "arn:aws:secretsmanager:us-west-2:123456789012:secret:my-path/my-secret-name-1a2b3c" + } + ] + } + } + } + } \ No newline at end of file diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 28dfce2462..f380d34c39 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -160,7 +160,8 @@ class TestTranslatorEndToEnd(TestCase): "cloudwatchlog", "streams", "sqs", - "amq", + "function_with_amq", + "function_with_amq_kms", "simpletable", "simpletable_with_sse", "implicit_api", From a617205f872a480c6e620fe140c13dacec38a420 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Fri, 15 Jan 2021 12:54:45 -0800 Subject: [PATCH 18/31] chore: bump version 1.34.0 (#1892) --- samtranslator/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index f31dd04782..4b449f5b1f 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.33.0" +__version__ = "1.34.0" From 4112fcfcc86e02d2081809c2b89e8a40a74486c7 Mon Sep 17 00:00:00 2001 From: mingkun2020 <68391979+mingkun2020@users.noreply.github.com> Date: Mon, 18 Jan 2021 15:20:27 -0800 Subject: [PATCH 19/31] test: Add simple integration tests (#1797) * set up integration test infrastructure for SAM translator * migrated basic integration tests Co-authored-by: Mathieu Grandis --- DEVELOPMENT_GUIDE.md | 4 + INTEGRATION_TESTS.md | 125 +++++ Makefile | 10 +- appveyor-integration-test.yml | 24 + integration/__init__.py | 0 integration/helpers/__init__.py | 0 integration/helpers/base_test.py | 376 +++++++++++++ integration/helpers/client_provider.py | 77 +++ integration/helpers/deployer/__init__.py | 0 integration/helpers/deployer/deployer.py | 494 ++++++++++++++++++ .../helpers/deployer/exceptions/__init__.py | 0 .../helpers/deployer/exceptions/exceptions.py | 65 +++ .../helpers/deployer/utils/__init__.py | 0 .../deployer/utils/artifact_exporter.py | 64 +++ integration/helpers/deployer/utils/colors.py | 94 ++++ .../helpers/deployer/utils/table_print.py | 168 ++++++ integration/helpers/deployer/utils/time.py | 128 +++++ integration/helpers/file_resources.py | 14 + integration/helpers/resource.py | 109 ++++ integration/helpers/template.py | 42 ++ integration/resources/code/code.zip | Bin 0 -> 224 bytes integration/resources/code/layer1.zip | Bin 0 -> 1963 bytes integration/resources/code/swagger1.json | 340 ++++++++++++ integration/resources/code/swagger2.json | 215 ++++++++ integration/resources/code/template.yaml | 8 + .../resources/expected/single/basic_api.json | 5 + .../single/basic_api_inline_openapi.json | 5 + .../single/basic_api_inline_swagger.json | 5 + .../single/basic_api_inline_with_cache.json | 5 + .../single/basic_api_inline_with_tags.json | 5 + .../expected/single/basic_api_with_tags.json | 5 + .../single/basic_application_s3_location.json | 3 + .../basic_application_sar_location.json | 3 + ...lication_sar_location_with_intrinsics.json | 4 + .../expected/single/basic_function.json | 4 + .../basic_function_event_destinations.json | 14 + .../single/basic_function_no_envvar.json | 4 + .../single/basic_function_openapi.json | 4 + .../single/basic_function_with_kmskeyarn.json | 5 + .../single/basic_function_with_sns_dlq.json | 5 + .../single/basic_function_with_sqs_dlq.json | 5 + .../single/basic_function_with_tags.json | 4 + .../single/basic_function_with_tracing.json | 6 + .../expected/single/basic_http_api.json | 4 + .../expected/single/basic_layer.json | 3 + .../single/basic_layer_with_parameters.json | 3 + ...basic_state_machine_inline_definition.json | 4 + .../single/basic_state_machine_with_tags.json | 4 + .../resources/templates/single/basic_api.yaml | 6 + .../single/basic_api_inline_openapi.yaml | 27 + .../single/basic_api_inline_swagger.yaml | 27 + .../templates/single/basic_api_with_tags.yaml | 9 + .../single/basic_application_s3_location.yaml | 5 + .../basic_application_sar_location.yaml | 9 + ...lication_sar_location_with_intrinsics.yaml | 57 ++ .../templates/single/basic_function.yaml | 15 + .../basic_function_event_destinations.yaml | 95 ++++ .../single/basic_function_no_envvar.yaml | 11 + .../single/basic_function_openapi.yaml | 18 + .../single/basic_function_with_kmskeyarn.yaml | 32 ++ .../single/basic_function_with_sns_dlq.yaml | 14 + .../single/basic_function_with_sqs_dlq.yaml | 15 + .../single/basic_function_with_tags.yaml | 14 + .../single/basic_function_with_tracing.yaml | 36 ++ .../templates/single/basic_http_api.yaml | 11 + .../templates/single/basic_layer.yaml | 6 + .../single/basic_layer_with_parameters.yaml | 46 ++ ...basic_state_machine_inline_definition.yaml | 23 + .../single/basic_state_machine_with_tags.yaml | 33 ++ .../single/basic_table_no_param.yaml | 4 + .../single/basic_table_with_param.yaml | 12 + integration/single/__init__.py | 1 + integration/single/test_basic_api.py | 79 +++ integration/single/test_basic_application.py | 45 ++ integration/single/test_basic_function.py | 181 +++++++ integration/single/test_basic_http_api.py | 18 + .../single/test_basic_layer_version.py | 43 ++ .../single/test_basic_state_machine.py | 43 ++ requirements/dev.txt | 5 + 79 files changed, 3403 insertions(+), 3 deletions(-) create mode 100644 INTEGRATION_TESTS.md create mode 100644 appveyor-integration-test.yml create mode 100644 integration/__init__.py create mode 100644 integration/helpers/__init__.py create mode 100644 integration/helpers/base_test.py create mode 100644 integration/helpers/client_provider.py create mode 100644 integration/helpers/deployer/__init__.py create mode 100644 integration/helpers/deployer/deployer.py create mode 100644 integration/helpers/deployer/exceptions/__init__.py create mode 100644 integration/helpers/deployer/exceptions/exceptions.py create mode 100644 integration/helpers/deployer/utils/__init__.py create mode 100644 integration/helpers/deployer/utils/artifact_exporter.py create mode 100644 integration/helpers/deployer/utils/colors.py create mode 100644 integration/helpers/deployer/utils/table_print.py create mode 100644 integration/helpers/deployer/utils/time.py create mode 100644 integration/helpers/file_resources.py create mode 100644 integration/helpers/resource.py create mode 100644 integration/helpers/template.py create mode 100644 integration/resources/code/code.zip create mode 100644 integration/resources/code/layer1.zip create mode 100644 integration/resources/code/swagger1.json create mode 100644 integration/resources/code/swagger2.json create mode 100644 integration/resources/code/template.yaml create mode 100644 integration/resources/expected/single/basic_api.json create mode 100644 integration/resources/expected/single/basic_api_inline_openapi.json create mode 100644 integration/resources/expected/single/basic_api_inline_swagger.json create mode 100644 integration/resources/expected/single/basic_api_inline_with_cache.json create mode 100644 integration/resources/expected/single/basic_api_inline_with_tags.json create mode 100644 integration/resources/expected/single/basic_api_with_tags.json create mode 100644 integration/resources/expected/single/basic_application_s3_location.json create mode 100644 integration/resources/expected/single/basic_application_sar_location.json create mode 100644 integration/resources/expected/single/basic_application_sar_location_with_intrinsics.json create mode 100644 integration/resources/expected/single/basic_function.json create mode 100644 integration/resources/expected/single/basic_function_event_destinations.json create mode 100644 integration/resources/expected/single/basic_function_no_envvar.json create mode 100644 integration/resources/expected/single/basic_function_openapi.json create mode 100644 integration/resources/expected/single/basic_function_with_kmskeyarn.json create mode 100644 integration/resources/expected/single/basic_function_with_sns_dlq.json create mode 100644 integration/resources/expected/single/basic_function_with_sqs_dlq.json create mode 100644 integration/resources/expected/single/basic_function_with_tags.json create mode 100644 integration/resources/expected/single/basic_function_with_tracing.json create mode 100644 integration/resources/expected/single/basic_http_api.json create mode 100644 integration/resources/expected/single/basic_layer.json create mode 100644 integration/resources/expected/single/basic_layer_with_parameters.json create mode 100644 integration/resources/expected/single/basic_state_machine_inline_definition.json create mode 100644 integration/resources/expected/single/basic_state_machine_with_tags.json create mode 100644 integration/resources/templates/single/basic_api.yaml create mode 100644 integration/resources/templates/single/basic_api_inline_openapi.yaml create mode 100644 integration/resources/templates/single/basic_api_inline_swagger.yaml create mode 100644 integration/resources/templates/single/basic_api_with_tags.yaml create mode 100644 integration/resources/templates/single/basic_application_s3_location.yaml create mode 100644 integration/resources/templates/single/basic_application_sar_location.yaml create mode 100644 integration/resources/templates/single/basic_application_sar_location_with_intrinsics.yaml create mode 100644 integration/resources/templates/single/basic_function.yaml create mode 100644 integration/resources/templates/single/basic_function_event_destinations.yaml create mode 100644 integration/resources/templates/single/basic_function_no_envvar.yaml create mode 100644 integration/resources/templates/single/basic_function_openapi.yaml create mode 100644 integration/resources/templates/single/basic_function_with_kmskeyarn.yaml create mode 100644 integration/resources/templates/single/basic_function_with_sns_dlq.yaml create mode 100644 integration/resources/templates/single/basic_function_with_sqs_dlq.yaml create mode 100644 integration/resources/templates/single/basic_function_with_tags.yaml create mode 100644 integration/resources/templates/single/basic_function_with_tracing.yaml create mode 100644 integration/resources/templates/single/basic_http_api.yaml create mode 100644 integration/resources/templates/single/basic_layer.yaml create mode 100644 integration/resources/templates/single/basic_layer_with_parameters.yaml create mode 100644 integration/resources/templates/single/basic_state_machine_inline_definition.yaml create mode 100644 integration/resources/templates/single/basic_state_machine_with_tags.yaml create mode 100644 integration/resources/templates/single/basic_table_no_param.yaml create mode 100644 integration/resources/templates/single/basic_table_with_param.yaml create mode 100644 integration/single/__init__.py create mode 100644 integration/single/test_basic_api.py create mode 100644 integration/single/test_basic_application.py create mode 100644 integration/single/test_basic_function.py create mode 100644 integration/single/test_basic_http_api.py create mode 100644 integration/single/test_basic_layer_version.py create mode 100644 integration/single/test_basic_state_machine.py diff --git a/DEVELOPMENT_GUIDE.md b/DEVELOPMENT_GUIDE.md index 6ccbb9ad67..de44b115c6 100644 --- a/DEVELOPMENT_GUIDE.md +++ b/DEVELOPMENT_GUIDE.md @@ -128,6 +128,10 @@ will not work in Python3.6). If you want to test in many versions, you can creat each version and flip between them (sourcing the activate script). Typically, we run all tests in one python version locally and then have our ci (appveyor) run all supported versions. +### Integration tests + +Integration tests are covered in detail in the [INTEGRATION_TESTS.md file](INTEGRATION_TESTS.md) of this repository. + Code Conventions ---------------- diff --git a/INTEGRATION_TESTS.md b/INTEGRATION_TESTS.md new file mode 100644 index 0000000000..c326c908bb --- /dev/null +++ b/INTEGRATION_TESTS.md @@ -0,0 +1,125 @@ +# AWS SAM integration tests + +These tests run SAM against AWS services by translating SAM templates, deploying them to Cloud Formation and verifying the resulting objects. + +They must run successfully under Python 2 and 3. + +## Run the tests + +### Prerequisites + +#### User and rights + +An Internet connection and an active AWS account are required to run the tests as they will interact with AWS services (most notably Cloud Formation) to create and update objects (Stacks, APIs, ...). + +AWS credentials must be configured either through a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) or [environment variables](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html) + +The user running the tests must have the following roles: + +``` +AmazonSQSFullAccess +AmazonSNSFullAccess +AmazonAPIGatewayAdministrator +AWSKeyManagementServiceFullAccess +AWSStepFunctionsFullAccess +``` + +If you plan on running the full tests suite, ensure that the user credentials you are running the tests with have a timeout of at least 30 minutes as the full suite can take more than 20 minutes to execute. + +#### Initialize the development environment + +If you haven't done so already, run the following command in a terminal at the root of the repository to initialize the development environment: + +``` +make init +``` + +### Running all the tests + +From the root of the repository, run: + +``` +make integ-test +``` + +### Running a specific test file + +From the command line, run: + +``` +pytest --no-cov path/to/the/test_file.py +``` + +For example, from the root of the project: + +```sh +pytest --no-cov integration/single/test_basic_api.py +``` + +### Running a specific test + +From the command line, run: + +``` +pytest --no-cov path/to/the/test_file.py::test_class::test_method +``` + +For example, from the root of the project: + +```sh +pytest --no-cov integration/single/test_basic_api.py::TestBasicApi::test_basic_api +``` + +*We don't measure coverage for integration tests.* + +## Write a test + +1. Add your test templates to the `integration/resources/templates` single or combination folder. +2. Write an expected json file for all the expected resources and add it to the `integration/resources/expected`. +3. (Optional) Add the resource files (zip, json, etc.) to `integration/resources/code` and update the dictionaries in `integration/helpers/file_resources.py`. +4. Write and add your python test code to the `integration` single or combination folder. +5. Run it! + +## Directory structure + +### Helpers + +Common classes and tools used by tests. + +``` ++-- helpers/ +| +-- deployer Tools to deploy to Cloud Formation +| +-- base_test.py Common class from which all test classes inherit +| +-- file_resources.py Files to upload to S3 +| +-- resource.py Helper functions to manipulate resources +| +-- template.py Helper functions to translate the template +``` + +`base_test.py` contains `setUpClass` and `tearDownClass` methods to respectively upload and clean the `file_resources.py` resources (upload the files to a new S3 bucket, empty and delete this bucket). + +### Resources + +File resources used by tests. + +``` ++-- resources +| +-- code Files to upload to S3 +| +-- expected Files describing the expected created resources +| +-- templates Source SAM templates to translate and deploy +``` + +The matching *expected* and *template* files should have the same name. + +For example, the `test_basic_api` test in the class `tests_integ/single/test_basic_api.py` takes `templates/single/basic_api.yaml` SAM template as input and verifies its result against `expected/single/basic_api.json`. + +### Single + +Basic tests which interact with only one service should be put here. + +### Combination + +Tests which interact with multiple services should be put there. + +### Tmp + +This directory is created on the first run and contains temporary and intermediary files used by the tests: sam templates with substituted variable values, translated temporary cloud formation templates, ... diff --git a/Makefile b/Makefile index c2b980b8f7..09dce252d5 100755 --- a/Makefile +++ b/Makefile @@ -6,13 +6,16 @@ init: pip install -e '.[dev]' test: - pytest --cov samtranslator --cov-report term-missing --cov-fail-under 95 tests + pytest --cov samtranslator --cov-report term-missing --cov-fail-under 95 tests/* + +integ-test: + pytest --no-cov integration/* black: - black setup.py samtranslator/* tests/* bin/*.py + black setup.py samtranslator/* tests/* integration/* bin/*.py black-check: - black --check setup.py samtranslator/* tests/* bin/*.py + black --check setup.py samtranslator/* tests/* integration/* bin/*.py # Command to run everytime you make changes to verify everything works dev: test @@ -30,6 +33,7 @@ Usage: $ make [TARGETS] TARGETS init Initialize and install the requirements and dev-requirements for this project. test Run the Unit tests. + integ-test Run the Integration tests. dev Run all development tests after a change. pr Perform all checks before submitting a Pull Request. diff --git a/appveyor-integration-test.yml b/appveyor-integration-test.yml new file mode 100644 index 0000000000..0dc1cdf12c --- /dev/null +++ b/appveyor-integration-test.yml @@ -0,0 +1,24 @@ +version: 1.0.{build} +image: Ubuntu + +environment: + matrix: + - TOXENV: py27 + PYTHON_VERSION: '2.7' + - TOXENV: py36 + PYTHON_VERSION: '3.6' + - TOXENV: py37 + PYTHON_VERSION: '3.7' + - TOXENV: py38 + PYTHON_VERSION: '3.8' + +build: off + +install: +- sh: "source ${HOME}/venv${PYTHON_VERSION}/bin/activate" +- sh: "python --version" +- make init + +test_script: +- make integ-test + diff --git a/integration/__init__.py b/integration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/helpers/__init__.py b/integration/helpers/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/helpers/base_test.py b/integration/helpers/base_test.py new file mode 100644 index 0000000000..82af15d1da --- /dev/null +++ b/integration/helpers/base_test.py @@ -0,0 +1,376 @@ +import logging +import os + +from integration.helpers.client_provider import ClientProvider +from integration.helpers.resource import generate_suffix, create_bucket, verify_stack_resources + +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path +from unittest.case import TestCase + +import boto3 +import pytest +import yaml +from botocore.exceptions import ClientError +from botocore.config import Config +from integration.helpers.deployer.deployer import Deployer +from integration.helpers.template import transform_template + +from integration.helpers.file_resources import FILE_TO_S3_URI_MAP, CODE_KEY_TO_FILE_MAP + +LOG = logging.getLogger(__name__) +STACK_NAME_PREFIX = "sam-integ-stack-" +S3_BUCKET_PREFIX = "sam-integ-bucket-" + + +class BaseTest(TestCase): + @classmethod + def setUpClass(cls): + cls.tests_integ_dir = Path(__file__).resolve().parents[1] + cls.resources_dir = Path(cls.tests_integ_dir, "resources") + cls.template_dir = Path(cls.resources_dir, "templates", "single") + cls.output_dir = Path(cls.tests_integ_dir, "tmp") + cls.expected_dir = Path(cls.resources_dir, "expected", "single") + cls.code_dir = Path(cls.resources_dir, "code") + cls.s3_bucket_name = S3_BUCKET_PREFIX + generate_suffix() + cls.session = boto3.session.Session() + cls.my_region = cls.session.region_name + cls.client_provider = ClientProvider() + cls.file_to_s3_uri_map = FILE_TO_S3_URI_MAP + cls.code_key_to_file = CODE_KEY_TO_FILE_MAP + + if not cls.output_dir.exists(): + os.mkdir(str(cls.output_dir)) + + cls._upload_resources(FILE_TO_S3_URI_MAP) + + @classmethod + def tearDownClass(cls): + cls._clean_bucket() + + @classmethod + def _clean_bucket(cls): + """ + Empties and deletes the bucket used for the tests + """ + s3 = boto3.resource("s3") + bucket = s3.Bucket(cls.s3_bucket_name) + object_summary_iterator = bucket.objects.all() + + for object_summary in object_summary_iterator: + try: + cls.client_provider.s3_client.delete_object(Key=object_summary.key, Bucket=cls.s3_bucket_name) + except ClientError as e: + LOG.error( + "Unable to delete object %s from bucket %s", object_summary.key, cls.s3_bucket_name, exc_info=e + ) + try: + cls.client_provider.s3_client.delete_bucket(Bucket=cls.s3_bucket_name) + except ClientError as e: + LOG.error("Unable to delete bucket %s", cls.s3_bucket_name, exc_info=e) + + @classmethod + def _upload_resources(cls, file_to_s3_uri_map): + """ + Creates the bucket and uploads the files used by the tests to it + """ + if not file_to_s3_uri_map or not file_to_s3_uri_map.items(): + LOG.debug("No resources to upload") + return + + create_bucket(cls.s3_bucket_name, region=cls.my_region) + + current_file_name = "" + + try: + for file_name, file_info in file_to_s3_uri_map.items(): + current_file_name = file_name + code_path = str(Path(cls.code_dir, file_name)) + LOG.debug("Uploading file %s to bucket %s", file_name, cls.s3_bucket_name) + s3_client = cls.client_provider.s3_client + s3_client.upload_file(code_path, cls.s3_bucket_name, file_name) + LOG.debug("File %s uploaded successfully to bucket %s", file_name, cls.s3_bucket_name) + file_info["uri"] = cls._get_s3_uri(file_name, file_info["type"]) + except ClientError as error: + LOG.error("Upload of file %s to bucket %s failed", current_file_name, cls.s3_bucket_name, exc_info=error) + cls._clean_bucket() + raise error + + @classmethod + def _get_s3_uri(cls, file_name, uri_type): + if uri_type == "s3": + return "s3://{}/{}".format(cls.s3_bucket_name, file_name) + + if cls.my_region == "us-east-1": + return "https://s3.amazonaws.com/{}/{}".format(cls.s3_bucket_name, file_name) + if cls.my_region == "us-iso-east-1": + return "https://s3.us-iso-east-1.c2s.ic.gov/{}/{}".format(cls.s3_bucket_name, file_name) + if cls.my_region == "us-isob-east-1": + return "https://s3.us-isob-east-1.sc2s.sgov.gov/{}/{}".format(cls.s3_bucket_name, file_name) + + return "https://s3-{}.amazonaws.com/{}/{}".format(cls.my_region, cls.s3_bucket_name, file_name) + + def setUp(self): + self.deployer = Deployer(self.client_provider.cfn_client) + + def tearDown(self): + self.client_provider.cfn_client.delete_stack(StackName=self.stack_name) + if os.path.exists(self.output_file_path): + os.remove(self.output_file_path) + if os.path.exists(self.sub_input_file_path): + os.remove(self.sub_input_file_path) + + def create_and_verify_stack(self, file_name, parameters=None): + """ + Creates the Cloud Formation stack and verifies it against the expected + result + + Parameters + ---------- + file_name : string + Template file name + parameters : list + List of parameters + """ + self.output_file_path = str(Path(self.output_dir, "cfn_" + file_name + ".yaml")) + self.expected_resource_path = str(Path(self.expected_dir, file_name + ".json")) + self.stack_name = STACK_NAME_PREFIX + file_name.replace("_", "-") + "-" + generate_suffix() + + self._fill_template(file_name) + self.transform_template() + self.deploy_stack(parameters) + self.verify_stack() + + def transform_template(self): + transform_template(self.sub_input_file_path, self.output_file_path) + + def get_region(self): + return self.my_region + + def get_s3_uri(self, file_name): + """ + Returns the S3 URI of a resource file + + Parameters + ---------- + file_name : string + Resource file name + """ + return self.file_to_s3_uri_map[file_name]["uri"] + + def get_code_key_s3_uri(self, code_key): + """ + Returns the S3 URI of a code key for template replacement + + Parameters + ---------- + code_key : string + Template code key + """ + return self.file_to_s3_uri_map[self.code_key_to_file[code_key]]["uri"] + + def get_stack_resources(self, resource_type, stack_resources=None): + if not stack_resources: + stack_resources = self.stack_resources + + resources = [] + for res in stack_resources["StackResourceSummaries"]: + if res["ResourceType"] == resource_type: + resources.append(res) + + return resources + + def get_stack_output(self, output_key): + for output in self.stack_description["Stacks"][0]["Outputs"]: + if output["OutputKey"] == output_key: + return output + return None + + def get_stack_tags(self, output_name): + resource_arn = self.get_stack_output(output_name)["OutputValue"] + return self.client_provider.sfn_client.list_tags_for_resource(resourceArn=resource_arn)["tags"] + + def get_stack_deployment_ids(self): + resources = self.get_stack_resources("AWS::ApiGateway::Deployment") + ids = [] + for res in resources: + ids.append(res["LogicalResourceId"]) + + return ids + + def get_api_stack_stages(self): + resources = self.get_stack_resources("AWS::ApiGateway::RestApi") + + if not resources: + return [] + + return self.client_provider.api_client.get_stages(restApiId=resources[0]["PhysicalResourceId"])["item"] + + def get_api_v2_stack_stages(self): + resources = self.get_stack_resources("AWS::ApiGatewayV2::Api") + + if not resources: + return [] + + return self.client_provider.api_v2_client.get_stages(ApiId=resources[0]["PhysicalResourceId"])["Items"] + + def get_stack_nested_stack_resources(self): + resources = self.get_stack_resources("AWS::CloudFormation::Stack") + + if not resources: + return None + + return self.client_provider.cfn_client.list_stack_resources(StackName=resources[0]["PhysicalResourceId"]) + + def get_stack_outputs(self): + if not self.stack_description: + return {} + output_list = self.stack_description["Stacks"][0]["Outputs"] + return {output["OutputKey"]: output["OutputValue"] for output in output_list} + + def get_resource_status_by_logical_id(self, logical_id): + if not self.stack_resources: + return None + + for res in self.stack_resources["StackResourceSummaries"]: + if res["LogicalResourceId"] == logical_id: + return res["ResourceStatus"] + + return None + + def get_physical_id_by_type(self, resource_type): + if not self.stack_resources: + return None + + for res in self.stack_resources["StackResourceSummaries"]: + if res["ResourceType"] == resource_type: + return res["PhysicalResourceId"] + + return None + + def get_logical_id_by_type(self, resource_type): + if not self.stack_resources: + return None + + for res in self.stack_resources["StackResourceSummaries"]: + if res["ResourceType"] == resource_type: + return res["LogicalResourceId"] + + return None + + def get_physical_id_by_logical_id(self, logical_id): + if not self.stack_resources: + return None + + for res in self.stack_resources["StackResourceSummaries"]: + if res["LogicalResourceId"] == logical_id: + return res["PhysicalResourceId"] + + return None + + def _fill_template(self, file_name): + """ + Replaces the template variables with their value + + Parameters + ---------- + file_name : string + Template file name + """ + input_file_path = str(Path(self.template_dir, file_name + ".yaml")) + updated_template_path = str(Path(self.output_dir, "sub_" + file_name + ".yaml")) + with open(input_file_path) as f: + data = f.read() + for key, _ in self.code_key_to_file.items(): + # We must double the {} to escape them so they will survive a round of unescape + data = data.replace("${{{}}}".format(key), self.get_code_key_s3_uri(key)) + yaml_doc = yaml.load(data, Loader=yaml.FullLoader) + + self._dump_yaml(updated_template_path, yaml_doc) + + self.sub_input_file_path = updated_template_path + + def set_template_resource_property(self, resource_name, property_name, value): + """ + Updates a resource property of the current SAM template + + Parameters + ---------- + resource_name: string + resource name + property_name: string + property name + value + value + """ + yaml_doc = self._load_yaml(self.sub_input_file_path) + yaml_doc["Resources"][resource_name]["Properties"][property_name] = value + self._dump_yaml(self.sub_input_file_path, yaml_doc) + + def get_template_resource_property(self, resource_name, property_name): + yaml_doc = self._load_yaml(self.sub_input_file_path) + return yaml_doc["Resources"][resource_name]["Properties"][property_name] + + def deploy_stack(self, parameters=None): + """ + Deploys the current cloud formation stack + """ + with open(self.output_file_path) as cfn_file: + result, changeset_type = self.deployer.create_and_wait_for_changeset( + stack_name=self.stack_name, + cfn_template=cfn_file.read(), + parameter_values=[] if parameters is None else parameters, + capabilities=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"], + role_arn=None, + notification_arns=[], + s3_uploader=None, + tags=[], + ) + self.deployer.execute_changeset(result["Id"], self.stack_name) + self.deployer.wait_for_execute(self.stack_name, changeset_type) + + self.stack_description = self.client_provider.cfn_client.describe_stacks(StackName=self.stack_name) + self.stack_resources = self.client_provider.cfn_client.list_stack_resources(StackName=self.stack_name) + + def verify_stack(self): + """ + Gets and compares the Cloud Formation stack against the expect result file + """ + # verify if the stack was successfully created + self.assertEqual(self.stack_description["Stacks"][0]["StackStatus"], "CREATE_COMPLETE") + # verify if the stack contains the expected resources + self.assertTrue(verify_stack_resources(self.expected_resource_path, self.stack_resources)) + + def _load_yaml(self, file_path): + """ + Loads a yaml file + + Parameters + ---------- + file_path : Path + File path + + Returns + ------- + Object + Yaml object + """ + with open(file_path) as f: + data = f.read() + return yaml.load(data, Loader=yaml.FullLoader) + + def _dump_yaml(self, file_path, yaml_doc): + """ + Writes a yaml object to a file + + Parameters + ---------- + file_path : Path + File path + yaml_doc : Object + Yaml object + """ + with open(file_path, "w") as f: + yaml.dump(yaml_doc, f) diff --git a/integration/helpers/client_provider.py b/integration/helpers/client_provider.py new file mode 100644 index 0000000000..2ffab0e19d --- /dev/null +++ b/integration/helpers/client_provider.py @@ -0,0 +1,77 @@ +import boto3 +from botocore.config import Config + + +class ClientProvider: + def __init__(self): + self._cloudformation_client = None + self._s3_client = None + self._api_client = None + self._lambda_client = None + self._iam_client = None + self._api_v2_client = None + self._sfn_client = None + + @property + def cfn_client(self): + """ + Cloudformation Client + """ + if not self._cloudformation_client: + config = Config(retries={"max_attempts": 10, "mode": "standard"}) + self._cloudformation_client = boto3.client("cloudformation", config=config) + return self._cloudformation_client + + @property + def s3_client(self): + """ + S3 Client + """ + if not self._s3_client: + self._s3_client = boto3.client("s3") + return self._s3_client + + @property + def api_client(self): + """ + APIGateway Client + """ + if not self._api_client: + self._api_client = boto3.client("apigateway") + return self._api_client + + @property + def lambda_client(self): + """ + Lambda Client + """ + if not self._lambda_client: + self._lambda_client = boto3.client("lambda") + return self._lambda_client + + @property + def iam_client(self): + """ + IAM Client + """ + if not self._iam_client: + self._iam_client = boto3.client("iam") + return self._iam_client + + @property + def api_v2_client(self): + """ + APIGatewayV2 Client + """ + if not self._api_v2_client: + self._api_v2_client = boto3.client("apigatewayv2") + return self._api_v2_client + + @property + def sfn_client(self): + """ + Step Functions Client + """ + if not self._sfn_client: + self._sfn_client = boto3.client("stepfunctions") + return self._sfn_client diff --git a/integration/helpers/deployer/__init__.py b/integration/helpers/deployer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/helpers/deployer/deployer.py b/integration/helpers/deployer/deployer.py new file mode 100644 index 0000000000..4cb0de31f6 --- /dev/null +++ b/integration/helpers/deployer/deployer.py @@ -0,0 +1,494 @@ +""" +Cloudformation deploy class which also streams events and changeset information +This was ported over from the sam-cli repo +""" + +# Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +# This is a modified version of the Deployer class from aws-sam-cli +# (and its dependencies) to work with python 2 +# Modifications: +# - Imports now reference local classes +# - Alternative imports for python2 +# - py3 -> py2 migrations (ex: "".format() instead of f"", no "from" for raise) +# - Moved UserException to exceptions.py +# - Moved DeployColor to colors.py +# - Removed unnecessary functions from artifact_exporter +import sys +import math +from collections import OrderedDict +import logging +import time +from datetime import datetime + +import botocore + +from integration.helpers.deployer.utils.colors import DeployColor +from integration.helpers.deployer.exceptions import exceptions as deploy_exceptions +from integration.helpers.deployer.utils.table_print import ( + pprint_column_names, + pprint_columns, + newline_per_item, + MIN_OFFSET, +) +from integration.helpers.deployer.utils.artifact_exporter import mktempfile, parse_s3_url +from integration.helpers.deployer.utils.time import utc_to_timestamp + +LOG = logging.getLogger(__name__) + +DESCRIBE_STACK_EVENTS_FORMAT_STRING = ( + "{ResourceStatus:<{0}} {ResourceType:<{1}} {LogicalResourceId:<{2}} {ResourceStatusReason:<{3}}" +) +DESCRIBE_STACK_EVENTS_DEFAULT_ARGS = OrderedDict( + { + "ResourceStatus": "ResourceStatus", + "ResourceType": "ResourceType", + "LogicalResourceId": "LogicalResourceId", + "ResourceStatusReason": "ResourceStatusReason", + } +) + +DESCRIBE_STACK_EVENTS_TABLE_HEADER_NAME = "CloudFormation events from changeset" + +DESCRIBE_CHANGESET_FORMAT_STRING = "{Operation:<{0}} {LogicalResourceId:<{1}} {ResourceType:<{2}} {Replacement:<{3}}" +DESCRIBE_CHANGESET_DEFAULT_ARGS = OrderedDict( + { + "Operation": "Operation", + "LogicalResourceId": "LogicalResourceId", + "ResourceType": "ResourceType", + "Replacement": "Replacement", + } +) + +DESCRIBE_CHANGESET_TABLE_HEADER_NAME = "CloudFormation stack changeset" + +OUTPUTS_FORMAT_STRING = "{Outputs:<{0}}" +OUTPUTS_DEFAULTS_ARGS = OrderedDict({"Outputs": "Outputs"}) + +OUTPUTS_TABLE_HEADER_NAME = "CloudFormation outputs from deployed stack" + + +class Deployer: + def __init__(self, cloudformation_client, changeset_prefix="sam-integ-"): + self._client = cloudformation_client + self.changeset_prefix = changeset_prefix + # 500ms of sleep time between stack checks and describe stack events. + self.client_sleep = 0.5 + # 2000ms of backoff time which is exponentially used, when there are exceptions during describe stack events + self.backoff = 2 + # Maximum number of attempts before raising exception back up the chain. + self.max_attempts = 3 + self.deploy_color = DeployColor() + + def has_stack(self, stack_name): + """ + Checks if a CloudFormation stack with given name exists + + :param stack_name: Name or ID of the stack + :return: True if stack exists. False otherwise + """ + try: + resp = self._client.describe_stacks(StackName=stack_name) + if not resp["Stacks"]: + return False + + # When you run CreateChangeSet on a a stack that does not exist, + # CloudFormation will create a stack and set it's status + # REVIEW_IN_PROGRESS. However this stack is cannot be manipulated + # by "update" commands. Under this circumstances, we treat like + # this stack does not exist and call CreateChangeSet will + # ChangeSetType set to CREATE and not UPDATE. + stack = resp["Stacks"][0] + return stack["StackStatus"] != "REVIEW_IN_PROGRESS" + + except botocore.exceptions.ClientError as e: + # If a stack does not exist, describe_stacks will throw an + # exception. Unfortunately we don't have a better way than parsing + # the exception msg to understand the nature of this exception. + + if "Stack with id {0} does not exist".format(stack_name) in str(e): + LOG.debug("Stack with id %s does not exist", stack_name) + return False + except botocore.exceptions.BotoCoreError as e: + # If there are credentials, environment errors, + # catch that and throw a deploy failed error. + + LOG.debug("Botocore Exception : %s", str(e)) + raise deploy_exceptions.DeployFailedError(stack_name=stack_name, msg=str(e)) + + except Exception as e: + # We don't know anything about this exception. Don't handle + LOG.debug("Unable to get stack details.", exc_info=e) + raise e + + def create_changeset( + self, stack_name, cfn_template, parameter_values, capabilities, role_arn, notification_arns, s3_uploader, tags + ): + """ + Call Cloudformation to create a changeset and wait for it to complete + + :param stack_name: Name or ID of stack + :param cfn_template: CloudFormation template string + :param parameter_values: Template parameters object + :param capabilities: Array of capabilities passed to CloudFormation + :param tags: Array of tags passed to CloudFormation + :return: + """ + if not self.has_stack(stack_name): + changeset_type = "CREATE" + # When creating a new stack, UsePreviousValue=True is invalid. + # For such parameters, users should either override with new value, + # or set a Default value in template to successfully create a stack. + parameter_values = [x for x in parameter_values if not x.get("UsePreviousValue", False)] + else: + changeset_type = "UPDATE" + # UsePreviousValue not valid if parameter is new + summary = self._client.get_template_summary(StackName=stack_name) + existing_parameters = [parameter["ParameterKey"] for parameter in summary["Parameters"]] + parameter_values = [ + x + for x in parameter_values + if not (x.get("UsePreviousValue", False) and x["ParameterKey"] not in existing_parameters) + ] + + # Each changeset will get a unique name based on time. + # Description is also setup based on current date and that SAM CLI is used. + kwargs = { + "ChangeSetName": self.changeset_prefix + str(int(time.time())), + "StackName": stack_name, + "TemplateBody": cfn_template, + "ChangeSetType": changeset_type, + "Parameters": parameter_values, + "Capabilities": capabilities, + "Description": "Created by SAM CLI at {0} UTC".format(datetime.utcnow().isoformat()), + "Tags": tags, + } + + # If an S3 uploader is available, use TemplateURL to deploy rather than + # TemplateBody. This is required for large templates. + if s3_uploader: + with mktempfile() as temporary_file: + temporary_file.write(kwargs.pop("TemplateBody")) + temporary_file.flush() + + # TemplateUrl property requires S3 URL to be in path-style format + parts = parse_s3_url( + s3_uploader.upload_with_dedup(temporary_file.name, "template"), version_property="Version" + ) + kwargs["TemplateURL"] = s3_uploader.to_path_style_s3_url(parts["Key"], parts.get("Version", None)) + + # don't set these arguments if not specified to use existing values + if role_arn is not None: + kwargs["RoleARN"] = role_arn + if notification_arns is not None: + kwargs["NotificationARNs"] = notification_arns + return self._create_change_set(stack_name=stack_name, changeset_type=changeset_type, **kwargs) + + def _create_change_set(self, stack_name, changeset_type, **kwargs): + try: + resp = self._client.create_change_set(**kwargs) + return resp, changeset_type + except botocore.exceptions.ClientError as ex: + if "The bucket you are attempting to access must be addressed using the specified endpoint" in str(ex): + raise deploy_exceptions.DeployBucketInDifferentRegionError( + "Failed to create/update stack {}".format(stack_name) + ) + raise deploy_exceptions.ChangeSetError(stack_name=stack_name, msg=str(ex)) + + except Exception as ex: + LOG.debug("Unable to create changeset", exc_info=ex) + raise deploy_exceptions.ChangeSetError(stack_name=stack_name, msg=str(ex)) + + @pprint_column_names( + format_string=DESCRIBE_CHANGESET_FORMAT_STRING, + format_kwargs=DESCRIBE_CHANGESET_DEFAULT_ARGS, + table_header=DESCRIBE_CHANGESET_TABLE_HEADER_NAME, + ) + def describe_changeset(self, change_set_id, stack_name, **kwargs): + """ + Call Cloudformation to describe a changeset + + :param change_set_id: ID of the changeset + :param stack_name: Name of the CloudFormation stack + :return: dictionary of changes described in the changeset. + """ + paginator = self._client.get_paginator("describe_change_set") + response_iterator = paginator.paginate(ChangeSetName=change_set_id, StackName=stack_name) + changes = {"Add": [], "Modify": [], "Remove": []} + changes_showcase = {"Add": "+ Add", "Modify": "* Modify", "Remove": "- Delete"} + changeset = False + for item in response_iterator: + cf_changes = item.get("Changes") + for change in cf_changes: + changeset = True + resource_props = change.get("ResourceChange") + action = resource_props.get("Action") + changes[action].append( + { + "LogicalResourceId": resource_props.get("LogicalResourceId"), + "ResourceType": resource_props.get("ResourceType"), + "Replacement": "N/A" + if resource_props.get("Replacement") is None + else resource_props.get("Replacement"), + } + ) + + for k, v in changes.items(): + for value in v: + row_color = self.deploy_color.get_changeset_action_color(action=k) + pprint_columns( + columns=[ + changes_showcase.get(k, k), + value["LogicalResourceId"], + value["ResourceType"], + value["Replacement"], + ], + width=kwargs["width"], + margin=kwargs["margin"], + format_string=DESCRIBE_CHANGESET_FORMAT_STRING, + format_args=kwargs["format_args"], + columns_dict=DESCRIBE_CHANGESET_DEFAULT_ARGS.copy(), + color=row_color, + ) + + if not changeset: + # There can be cases where there are no changes, + # but could be an an addition of a SNS notification topic. + pprint_columns( + columns=["-", "-", "-", "-"], + width=kwargs["width"], + margin=kwargs["margin"], + format_string=DESCRIBE_CHANGESET_FORMAT_STRING, + format_args=kwargs["format_args"], + columns_dict=DESCRIBE_CHANGESET_DEFAULT_ARGS.copy(), + ) + + return changes + + def wait_for_changeset(self, changeset_id, stack_name): + """ + Waits until the changeset creation completes + + :param changeset_id: ID or name of the changeset + :param stack_name: Stack name + :return: Latest status of the create-change-set operation + """ + sys.stdout.write("\nWaiting for changeset to be created..\n") + sys.stdout.flush() + + # Wait for changeset to be created + waiter = self._client.get_waiter("change_set_create_complete") + # Poll every 5 seconds. Changeset creation should be fast + waiter_config = {"Delay": 5} + try: + waiter.wait(ChangeSetName=changeset_id, StackName=stack_name, WaiterConfig=waiter_config) + except botocore.exceptions.WaiterError as ex: + + resp = ex.last_response + status = resp["Status"] + reason = resp["StatusReason"] + + if ( + status == "FAILED" + and "The submitted information didn't contain changes." in reason + or "No updates are to be performed" in reason + ): + raise deploy_exceptions.ChangeEmptyError(stack_name=stack_name) + + raise deploy_exceptions.ChangeSetError( + stack_name=stack_name, msg="ex: {0} Status: {1}. Reason: {2}".format(ex, status, reason) + ) + + def execute_changeset(self, changeset_id, stack_name): + """ + Calls CloudFormation to execute changeset + + :param changeset_id: ID of the changeset + :param stack_name: Name or ID of the stack + :return: Response from execute-change-set call + """ + try: + return self._client.execute_change_set(ChangeSetName=changeset_id, StackName=stack_name) + except botocore.exceptions.ClientError as ex: + raise deploy_exceptions.DeployFailedError(stack_name=stack_name, msg=str(ex)) + + def get_last_event_time(self, stack_name): + """ + Finds the last event time stamp thats present for the stack, if not get the current time + :param stack_name: Name or ID of the stack + :return: unix epoch + """ + try: + return utc_to_timestamp( + self._client.describe_stack_events(StackName=stack_name)["StackEvents"][0]["Timestamp"] + ) + except KeyError: + return time.time() + + @pprint_column_names( + format_string=DESCRIBE_STACK_EVENTS_FORMAT_STRING, + format_kwargs=DESCRIBE_STACK_EVENTS_DEFAULT_ARGS, + table_header=DESCRIBE_STACK_EVENTS_TABLE_HEADER_NAME, + ) + def describe_stack_events(self, stack_name, time_stamp_marker, **kwargs): + """ + Calls CloudFormation to get current stack events + :param stack_name: Name or ID of the stack + :param time_stamp_marker: last event time on the stack to start streaming events from. + :return: + """ + + stack_change_in_progress = True + events = set() + retry_attempts = 0 + + while stack_change_in_progress and retry_attempts <= self.max_attempts: + try: + + # Only sleep if there have been no retry_attempts + time.sleep(self.client_sleep if retry_attempts == 0 else 0) + describe_stacks_resp = self._client.describe_stacks(StackName=stack_name) + paginator = self._client.get_paginator("describe_stack_events") + response_iterator = paginator.paginate(StackName=stack_name) + stack_status = describe_stacks_resp["Stacks"][0]["StackStatus"] + latest_time_stamp_marker = time_stamp_marker + for event_items in response_iterator: + for event in event_items["StackEvents"]: + if event["EventId"] not in events and utc_to_timestamp(event["Timestamp"]) > time_stamp_marker: + events.add(event["EventId"]) + latest_time_stamp_marker = max( + latest_time_stamp_marker, utc_to_timestamp(event["Timestamp"]) + ) + row_color = self.deploy_color.get_stack_events_status_color(status=event["ResourceStatus"]) + pprint_columns( + columns=[ + event["ResourceStatus"], + event["ResourceType"], + event["LogicalResourceId"], + event.get("ResourceStatusReason", "-"), + ], + width=kwargs["width"], + margin=kwargs["margin"], + format_string=DESCRIBE_STACK_EVENTS_FORMAT_STRING, + format_args=kwargs["format_args"], + columns_dict=DESCRIBE_STACK_EVENTS_DEFAULT_ARGS.copy(), + color=row_color, + ) + # Skip already shown old event entries + elif utc_to_timestamp(event["Timestamp"]) <= time_stamp_marker: + time_stamp_marker = latest_time_stamp_marker + break + else: # go to next loop if not break from inside loop + time_stamp_marker = latest_time_stamp_marker # update marker if all events are new + continue + break # reached here only if break from inner loop! + + if self._check_stack_complete(stack_status): + stack_change_in_progress = False + break + except botocore.exceptions.ClientError as ex: + retry_attempts = retry_attempts + 1 + if retry_attempts > self.max_attempts: + LOG.error("Describing stack events for %s failed: %s", stack_name, str(ex)) + return + # Sleep in exponential backoff mode + time.sleep(math.pow(self.backoff, retry_attempts)) + + def _check_stack_complete(self, status): + return "COMPLETE" in status and "CLEANUP" not in status + + def wait_for_execute(self, stack_name, changeset_type): + sys.stdout.write( + "\n{} - Waiting for stack create/update " + "to complete\n".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + ) + sys.stdout.flush() + + self.describe_stack_events(stack_name, self.get_last_event_time(stack_name)) + + # Pick the right waiter + if changeset_type == "CREATE": + waiter = self._client.get_waiter("stack_create_complete") + elif changeset_type == "UPDATE": + waiter = self._client.get_waiter("stack_update_complete") + else: + raise RuntimeError("Invalid changeset type {0}".format(changeset_type)) + + # Poll every 30 seconds. Polling too frequently risks hitting rate limits + # on CloudFormation's DescribeStacks API + waiter_config = {"Delay": 30, "MaxAttempts": 120} + + try: + waiter.wait(StackName=stack_name, WaiterConfig=waiter_config) + except botocore.exceptions.WaiterError as ex: + LOG.debug("Execute changeset waiter exception", exc_info=ex) + + raise deploy_exceptions.DeployFailedError(stack_name=stack_name, msg=str(ex)) + + outputs = self.get_stack_outputs(stack_name=stack_name, echo=False) + if outputs: + self._display_stack_outputs(outputs) + + def create_and_wait_for_changeset( + self, stack_name, cfn_template, parameter_values, capabilities, role_arn, notification_arns, s3_uploader, tags + ): + try: + result, changeset_type = self.create_changeset( + stack_name, cfn_template, parameter_values, capabilities, role_arn, notification_arns, s3_uploader, tags + ) + self.wait_for_changeset(result["Id"], stack_name) + self.describe_changeset(result["Id"], stack_name) + return result, changeset_type + except botocore.exceptions.ClientError as ex: + raise deploy_exceptions.DeployFailedError(stack_name=stack_name, msg=str(ex)) + + @pprint_column_names( + format_string=OUTPUTS_FORMAT_STRING, format_kwargs=OUTPUTS_DEFAULTS_ARGS, table_header=OUTPUTS_TABLE_HEADER_NAME + ) + def _display_stack_outputs(self, stack_outputs, **kwargs): + for counter, output in enumerate(stack_outputs): + for k, v in [ + ("Key", output.get("OutputKey")), + ("Description", output.get("Description", "-")), + ("Value", output.get("OutputValue")), + ]: + pprint_columns( + columns=["{k:<{0}}{v:<{0}}".format(MIN_OFFSET, k=k, v=v)], + width=kwargs["width"], + margin=kwargs["margin"], + format_string=OUTPUTS_FORMAT_STRING, + format_args=kwargs["format_args"], + columns_dict=OUTPUTS_DEFAULTS_ARGS.copy(), + color="green", + replace_whitespace=False, + break_long_words=False, + drop_whitespace=False, + ) + newline_per_item(stack_outputs, counter) + + def get_stack_outputs(self, stack_name, echo=True): + try: + stacks_description = self._client.describe_stacks(StackName=stack_name) + try: + outputs = stacks_description["Stacks"][0]["Outputs"] + if echo: + sys.stdout.write("\nStack {stack_name} outputs:\n".format(stack_name=stack_name)) + sys.stdout.flush() + self._display_stack_outputs(stack_outputs=outputs) + return outputs + except KeyError: + return None + + except botocore.exceptions.ClientError as ex: + raise deploy_exceptions.DeployStackOutPutFailedError(stack_name=stack_name, msg=str(ex)) diff --git a/integration/helpers/deployer/exceptions/__init__.py b/integration/helpers/deployer/exceptions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/helpers/deployer/exceptions/exceptions.py b/integration/helpers/deployer/exceptions/exceptions.py new file mode 100644 index 0000000000..3dee92caa3 --- /dev/null +++ b/integration/helpers/deployer/exceptions/exceptions.py @@ -0,0 +1,65 @@ +""" +Exceptions that are raised by sam deploy +This was ported over from the sam-cli repo +""" +import click + + +class UserException(click.ClickException): + """ + Base class for all exceptions that need to be surfaced to the user. Typically, we will display the exception + message to user and return the error code from CLI process + """ + + exit_code = 1 + + def __init__(self, message, wrapped_from=None): + self.wrapped_from = wrapped_from + + click.ClickException.__init__(self, message) + + +class ChangeEmptyError(UserException): + def __init__(self, stack_name): + self.stack_name = stack_name + message_fmt = "No changes to deploy. Stack {stack_name} is up to date" + super(ChangeEmptyError, self).__init__(message=message_fmt.format(stack_name=self.stack_name)) + + +class ChangeSetError(UserException): + def __init__(self, stack_name, msg): + self.stack_name = stack_name + self.msg = msg + message_fmt = "Failed to create changeset for the stack: {stack_name}, {msg}" + super(ChangeSetError, self).__init__(message=message_fmt.format(stack_name=self.stack_name, msg=self.msg)) + + +class DeployFailedError(UserException): + def __init__(self, stack_name, msg): + self.stack_name = stack_name + self.msg = msg + + message_fmt = "Failed to create/update the stack: {stack_name}, {msg}" + + super(DeployFailedError, self).__init__(message=message_fmt.format(stack_name=self.stack_name, msg=msg)) + + +class DeployStackOutPutFailedError(UserException): + def __init__(self, stack_name, msg): + self.stack_name = stack_name + self.msg = msg + + message_fmt = "Failed to get outputs from stack: {stack_name}, {msg}" + + super(DeployStackOutPutFailedError, self).__init__( + message=message_fmt.format(stack_name=self.stack_name, msg=msg) + ) + + +class DeployBucketInDifferentRegionError(UserException): + def __init__(self, msg): + self.msg = msg + + message_fmt = "{msg} : deployment s3 bucket is in a different region, try sam deploy --guided" + + super(DeployBucketInDifferentRegionError, self).__init__(message=message_fmt.format(msg=self.msg)) diff --git a/integration/helpers/deployer/utils/__init__.py b/integration/helpers/deployer/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration/helpers/deployer/utils/artifact_exporter.py b/integration/helpers/deployer/utils/artifact_exporter.py new file mode 100644 index 0000000000..6a06dfdbbb --- /dev/null +++ b/integration/helpers/deployer/utils/artifact_exporter.py @@ -0,0 +1,64 @@ +""" +Logic for uploading to S3 per Cloudformation Specific Resource +This was ported over from the sam-cli repo +""" +# pylint: disable=no-member + +# Copyright 2012-2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +import os +import tempfile +import contextlib +from contextlib import contextmanager + +try: + from urllib.parse import urlparse, parse_qs +except ImportError: # py2 + from urlparse import urlparse, parse_qs +import uuid + + +def parse_s3_url(url, bucket_name_property="Bucket", object_key_property="Key", version_property=None): + + if isinstance(url, str) and url.startswith("s3://"): + + parsed = urlparse(url) + query = parse_qs(parsed.query) + + if parsed.netloc and parsed.path: + result = dict() + result[bucket_name_property] = parsed.netloc + result[object_key_property] = parsed.path.lstrip("/") + + # If there is a query string that has a single versionId field, + # set the object version and return + if version_property is not None and "versionId" in query and len(query["versionId"]) == 1: + result[version_property] = query["versionId"][0] + + return result + + raise ValueError("URL given to the parse method is not a valid S3 url " "{0}".format(url)) + + +@contextmanager +def mktempfile(): + directory = tempfile.gettempdir() + filename = os.path.join(directory, uuid.uuid4().hex) + + try: + with open(filename, "w+") as handle: + yield handle + finally: + if os.path.exists(filename): + os.remove(filename) diff --git a/integration/helpers/deployer/utils/colors.py b/integration/helpers/deployer/utils/colors.py new file mode 100644 index 0000000000..a653e22533 --- /dev/null +++ b/integration/helpers/deployer/utils/colors.py @@ -0,0 +1,94 @@ +""" +Wrapper to generated colored messages for printing in Terminal +This was ported over from the sam-cli repo +""" + +import click + + +class Colored: + """ + Helper class to add ANSI colors and decorations to text. Given a string, ANSI colors are added with special prefix + and suffix characters that are specially interpreted by Terminals to display colors. + + Ex: "message" -> add red color -> \x1b[31mmessage\x1b[0m + + This class serves two purposes: + - Hide the underlying library used to provide colors: In this case, we use ``click`` library which is usually + used to build a CLI interface. We use ``click`` just to minimize the number of dependencies we add to this + project. This class allows us to replace click with any other color library like ``pygments`` without + changing callers. + + - Transparently turn off colors: In cases when the string is not written to Terminal (ex: log file) the ANSI + color codes should not be written. This class supports the scenario by allowing you to turn off colors. + Calls to methods like `red()` will simply return the input string. + """ + + def __init__(self, colorize=True): + """ + Initialize the object + + Parameters + ---------- + colorize : bool + Optional. Set this to True to turn on coloring. False will turn off coloring + """ + self.colorize = colorize + + def red(self, msg): + """Color the input red""" + return self._color(msg, "red") + + def green(self, msg): + """Color the input green""" + return self._color(msg, "green") + + def cyan(self, msg): + """Color the input cyan""" + return self._color(msg, "cyan") + + def white(self, msg): + """Color the input white""" + return self._color(msg, "white") + + def yellow(self, msg): + """Color the input yellow""" + return self._color(msg, "yellow") + + def underline(self, msg): + """Underline the input""" + return click.style(msg, underline=True) if self.colorize else msg + + def _color(self, msg, color): + """Internal helper method to add colors to input""" + kwargs = {"fg": color} + return click.style(msg, **kwargs) if self.colorize else msg + + +class DeployColor: + def __init__(self): + self._color = Colored() + self.changeset_color_map = {"Add": "green", "Modify": "yellow", "Remove": "red"} + self.status_color_map = { + "CREATE_COMPLETE": "green", + "CREATE_FAILED": "red", + "CREATE_IN_PROGRESS": "yellow", + "DELETE_COMPLETE": "green", + "DELETE_FAILED": "red", + "DELETE_IN_PROGRESS": "red", + "REVIEW_IN_PROGRESS": "yellow", + "ROLLBACK_COMPLETE": "red", + "ROLLBACK_IN_PROGRESS": "red", + "UPDATE_COMPLETE": "green", + "UPDATE_COMPLETE_CLEANUP_IN_PROGRESS": "yellow", + "UPDATE_IN_PROGRESS": "yellow", + "UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS": "red", + "UPDATE_ROLLBACK_FAILED": "red", + "UPDATE_ROLLBACK_IN_PROGRESS": "red", + } + + def get_stack_events_status_color(self, status): + return self.status_color_map.get(status, "yellow") + + def get_changeset_action_color(self, action): + return self.changeset_color_map.get(action, "yellow") diff --git a/integration/helpers/deployer/utils/table_print.py b/integration/helpers/deployer/utils/table_print.py new file mode 100644 index 0000000000..4cc27d6f30 --- /dev/null +++ b/integration/helpers/deployer/utils/table_print.py @@ -0,0 +1,168 @@ +""" +Utilities for table pretty printing using click +This was ported over from the sam-cli repo +""" +from itertools import count + +try: + from itertools import zip_longest +except ImportError: # py2 + from itertools import izip_longest as zip_longest +import textwrap +from functools import wraps + +import click + +MIN_OFFSET = 20 + + +def pprint_column_names(format_string, format_kwargs, margin=None, table_header=None, color="yellow"): + """ + Prints column names + + Parameters + ---------- + format_string : str + format string to be used that has the strings, minimum width to be replaced + format_kwargs : list + dictionary that is supplied to the format_string to format the string + margin : int, optional + margin that is to be reduced from column width for columnar text, by default None + table_header : str, optional + Text to display before the table, by default None + color : str, optional + Table color, by default "yellow" + + Returns + ------- + str + Complete table string representation + + Raises + ------ + ValueError + format_kwargs is empty + ValueError + [description] + """ + min_width = 100 + min_margin = 2 + + def pprint_wrap(func): + # Calculate terminal width, number of columns in the table + width, _ = click.get_terminal_size() + # For UX purposes, set a minimum width for the table to be usable + # and usable_width keeps margins in mind. + width = max(width, min_width) + + total_args = len(format_kwargs) + if not total_args: + raise ValueError("Number of arguments supplied should be > 0 , format_kwargs: {}".format(format_kwargs)) + + # Get width to be a usable number so that we can equally divide the space for all the columns. + # Can be refactored, to allow for modularity in the shaping of the columns. + width = width - (width % total_args) + usable_width_no_margin = int(width) - 1 + usable_width = int((usable_width_no_margin - (margin if margin else min_margin))) + if total_args > int(usable_width / 2): + raise ValueError("Total number of columns exceed available width") + width_per_column = int(usable_width / total_args) + + # The final column should not roll over into the next line + final_arg_width = width_per_column - 1 + + # the format string contains minimumwidth that need to be set. + # eg: "{a:{0}}} {b:<{1}}} {c:{2}}}" + format_args = [width_per_column for _ in range(total_args - 1)] + format_args.extend([final_arg_width]) + + # format arguments are now ready for setting minimumwidth + + @wraps(func) + def wrap(*args, **kwargs): + # The table is setup with the column names, format_string contains the column names. + if table_header: + click.secho("\n" + table_header) + click.secho("-" * usable_width, fg=color) + click.secho(format_string.format(*format_args, **format_kwargs), fg=color) + click.secho("-" * usable_width, fg=color) + # format_args which have the minimumwidth set per {} in the format_string is passed to the function + # which this decorator wraps, so that the function has access to the correct format_args + kwargs["format_args"] = format_args + kwargs["width"] = width_per_column + kwargs["margin"] = margin if margin else min_margin + result = func(*args, **kwargs) + # Complete the table + click.secho("-" * usable_width, fg=color) + return result + + return wrap + + return pprint_wrap + + +def wrapped_text_generator(texts, width, margin, **textwrap_kwargs): + """ + Returns a generator where the contents are wrapped text to a specified width + + Parameters + ---------- + texts : list + list of text that needs to be wrapped at specified width + width : int + width of the text to be wrapped + margin : int + margin to be reduced from width for cleaner UX + + Yields + ------- + func + generator of wrapped text + """ + for text in texts: + yield textwrap.wrap(text, width=width - margin, **textwrap_kwargs) + + +def pprint_columns(columns, width, margin, format_string, format_args, columns_dict, color="yellow", **textwrap_kwargs): + """ + Prints columns based on list of columnar text, associated formatting string and associated format arguments. + + Parameters + ---------- + columns : list + List of columnnar text that go into columns as specified by the format_string + width : int + Width of the text to be wrapped + margin : int + Margin to be reduced from width for cleaner UX + format_string : str + Format string that has both width and text specifiers set. + format_args : list + List of offset specifiers + columns_dict : dict + Arguments dictionary that have dummy values per column + color : str, optional + Rows color, by default "yellow" + """ + for columns_text in zip_longest(*wrapped_text_generator(columns, width, margin, **textwrap_kwargs), fillvalue=""): + counter = count() + # Generate columnar data that correspond to the column names and update them. + for k, _ in columns_dict.items(): + columns_dict[k] = columns_text[next(counter)] + + click.secho(format_string.format(*format_args, **columns_dict), fg=color) + + +def newline_per_item(iterable, counter): + """ + Adds a new line based on the index of a given iterable + + Parameters + ---------- + iterable : iterable + Any iterable that implements __len__ + counter : int + Current index within the iterable + """ + if counter < len(iterable) - 1: + click.echo(message="", nl=True) diff --git a/integration/helpers/deployer/utils/time.py b/integration/helpers/deployer/utils/time.py new file mode 100644 index 0000000000..6a5706aaef --- /dev/null +++ b/integration/helpers/deployer/utils/time.py @@ -0,0 +1,128 @@ +""" +Date & Time related utilities +This was ported over from the sam-cli repo +""" + +import datetime +import dateparser + +from dateutil.tz import tzutc + + +def timestamp_to_iso(timestamp): + """ + Convert Unix Epoch Timestamp to ISO formatted time string: + Ex: 1234567890 -> 2018-07-05T03:09:43.842000 + + Parameters + ---------- + timestamp : int + Unix epoch timestamp + + Returns + ------- + str + ISO formatted time string + """ + + return to_datetime(timestamp).isoformat() + + +def to_datetime(timestamp): + """ + Convert Unix Epoch Timestamp to Python's ``datetime.datetime`` object + + Parameters + ---------- + timestamp : int + Unix epoch timestamp + + Returns + ------- + datetime.datetime + Datetime representation of timestamp + """ + + timestamp_secs = int(timestamp) / 1000.0 + return datetime.datetime.utcfromtimestamp(timestamp_secs) + + +def to_timestamp(some_time): + """ + Converts the given datetime value to Unix timestamp + + Parameters + ---------- + some_time : datetime.datetime + Value to be converted to unix epoch. This must be without any timezone identifier + + Returns + ------- + int + Unix timestamp of the given time + """ + + # `total_seconds()` returns elaped microseconds as a float. Get just milliseconds and discard the rest. + return int((some_time - datetime.datetime(1970, 1, 1)).total_seconds() * 1000.0) + + +def utc_to_timestamp(utc): + """ + Converts utc timestamp with tz_info set to utc to Unix timestamp + :param utc: datetime.datetime + :return: UNIX timestamp + """ + + return to_timestamp(utc.replace(tzinfo=None)) + + +def to_utc(some_time): + """ + Convert the given date to UTC, if the date contains a timezone. + + Parameters + ---------- + some_time : datetime.datetime + datetime object to convert to UTC + + Returns + ------- + datetime.datetime + Converted datetime object + """ + + # Convert timezone aware objects to UTC + if some_time.tzinfo and some_time.utcoffset(): + some_time = some_time.astimezone(tzutc()) + + # Now that time is UTC, simply remove the timezone component. + return some_time.replace(tzinfo=None) + + +def parse_date(date_string): + """ + Parse the given string as datetime object. This parser supports in almost any string formats. + + For relative times, like `10min ago`, this parser computes the actual time relative to current UTC time. This + allows time to always be in UTC if an explicit time zone is not provided. + + Parameters + ---------- + date_string : str + String representing the date + + Returns + ------- + datetime.datetime + Parsed datetime object. None, if the string cannot be parsed. + """ + + parser_settings = { + # Relative times like '10m ago' must subtract from the current UTC time. Without this setting, dateparser + # will use current local time as the base for subtraction, but falsely assume it is a UTC time. Therefore + # the time that dateparser returns will be a `datetime` object that did not have any timezone information. + # So be explicit to set the time to UTC. + "RELATIVE_BASE": datetime.datetime.utcnow() + } + + return dateparser.parse(date_string, settings=parser_settings) diff --git a/integration/helpers/file_resources.py b/integration/helpers/file_resources.py new file mode 100644 index 0000000000..eadd533363 --- /dev/null +++ b/integration/helpers/file_resources.py @@ -0,0 +1,14 @@ +FILE_TO_S3_URI_MAP = { + "code.zip": {"type": "s3", "uri": ""}, + "layer1.zip": {"type": "s3", "uri": ""}, + "swagger1.json": {"type": "s3", "uri": ""}, + "swagger2.json": {"type": "s3", "uri": ""}, + "template.yaml": {"type": "http", "uri": ""}, +} + +CODE_KEY_TO_FILE_MAP = { + "codeuri": "code.zip", + "contenturi": "layer1.zip", + "definitionuri": "swagger1.json", + "templateurl": "template.yaml", +} diff --git a/integration/helpers/resource.py b/integration/helpers/resource.py new file mode 100644 index 0000000000..8a32a1de63 --- /dev/null +++ b/integration/helpers/resource.py @@ -0,0 +1,109 @@ +import json +import re +import random +import string # pylint: disable=deprecated-module + +import boto3 +from botocore.exceptions import ClientError, NoRegionError + +from samtranslator.translator.logical_id_generator import LogicalIdGenerator + +# Length of the random suffix added at the end of the resources we create +# to avoid collisions between tests +RANDOM_SUFFIX_LENGTH = 12 + + +def verify_stack_resources(expected_file_path, stack_resources): + """ + Verifies that the stack resources match the expected ones + + Parameters + ---------- + expected_file_path : Path + Path to the file containing the expected resources + stack_resources : List + Stack resources + + Returns + ------- + bool + True if the stack resources exactly match the expected ones, False otherwise + """ + with open(expected_file_path) as expected_data: + expected_resources = _sort_resources(json.load(expected_data)) + parsed_resources = _sort_resources(stack_resources["StackResourceSummaries"]) + + if len(expected_resources) != len(parsed_resources): + return False + + for i in range(len(expected_resources)): + exp = expected_resources[i] + parsed = parsed_resources[i] + if not re.match( + "^" + exp["LogicalResourceId"] + "([0-9a-f]{" + str(LogicalIdGenerator.HASH_LENGTH) + "})?$", + parsed["LogicalResourceId"], + ): + return False + if exp["ResourceType"] != parsed["ResourceType"]: + return False + return True + + +def generate_suffix(): + """ + Generates a basic random string of length RANDOM_SUFFIX_LENGTH + to append to objects names used in the tests to avoid collisions + between tests runs + + Returns + ------- + string + Random lowercase alphanumeric string of length RANDOM_SUFFIX_LENGTH + """ + return "".join(random.choice(string.ascii_lowercase) for i in range(RANDOM_SUFFIX_LENGTH)) + + +def _sort_resources(resources): + """ + Sorts a stack's resources by LogicalResourceId + + Parameters + ---------- + resources : list + Resources to sort + + Returns + ------- + list + List of resources, sorted + """ + if resources is None: + return [] + return sorted(resources, key=lambda d: d["LogicalResourceId"]) + + +def create_bucket(bucket_name, region): + """ + Creates a S3 bucket in a specific region + + Parameters + ---------- + bucket_name : string + Bucket name + region : string + Region name + + Raises + ------ + NoRegionError + If region is not specified + """ + if region is None: + raise NoRegionError() + if region == "us-east-1": + s3_client = boto3.client("s3") + s3_client.create_bucket(Bucket=bucket_name) + else: + s3_client = boto3.client("s3", region_name=region) + location = {"LocationConstraint": region} + s3_client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration=location) diff --git a/integration/helpers/template.py b/integration/helpers/template.py new file mode 100644 index 0000000000..f75bc4c56e --- /dev/null +++ b/integration/helpers/template.py @@ -0,0 +1,42 @@ +import json +import logging +from functools import reduce + +import boto3 + +from samtranslator.model.exceptions import InvalidDocumentException +from samtranslator.translator.managed_policy_translator import ManagedPolicyLoader +from samtranslator.translator.transform import transform +from samtranslator.yaml_helper import yaml_parse + + +def transform_template(sam_template_path, cfn_output_path): + """ + Locally transforms a SAM template to a Cloud Formation template + + Parameters + ---------- + sam_template_path : Path + SAM template input path + cfn_output_path : Path + Cloud formation template output path + """ + LOG = logging.getLogger(__name__) + iam_client = boto3.client("iam") + + with open(sam_template_path) as f: + sam_template = yaml_parse(f) + + try: + cloud_formation_template = transform(sam_template, {}, ManagedPolicyLoader(iam_client)) + cloud_formation_template_prettified = json.dumps(cloud_formation_template, indent=2) + + with open(cfn_output_path, "w") as f: + f.write(cloud_formation_template_prettified) + + print("Wrote transformed CloudFormation template to: " + cfn_output_path) + except InvalidDocumentException as e: + error_message = reduce(lambda message, error: message + " " + error.message, e.causes, e.message) + LOG.error(error_message) + errors = map(lambda cause: cause.message, e.causes) + LOG.error(errors) diff --git a/integration/resources/code/code.zip b/integration/resources/code/code.zip new file mode 100644 index 0000000000000000000000000000000000000000..3f21de8b6cf8b2ebb0ff6893c9eca604429098b1 GIT binary patch literal 224 zcmWIWW@h1H00E{NE6!sx9 zrD`Z;q~_%0D`{GD)p7-RGcw6BZ zKwMhE&A`agrR*usa;BQO@a@cKADK{zX-l_U zI#IM-XS21k&z!k8-<^5qq{)`NC97xI)g$fKrXMq3rKYBK?8lR>p1q#RAH}{p#@Cog z`PN)AHcsbs&Di8`TK4bcoBDG##n0~475kmjVMxdi%45@DX5foauTF?AeDh;^&6&?H z8cpmQ*tl90`MeWy|LgeAv#I>_B&pEX}0z(GuUUY-_@t{E_vQ= zP5wl=1i6%budFuxt~paYv-k|>XNJ!TpF^+TT+*8Jxp7TOinWN!8Rj$1k)}5%FR%9c zc5knQPk^G^_qVgZzvoV9@C!D4Wu)>pLM8W@@+`rHm3eNDW@SbB`o=HTS2xeUp4a!> z(r&)J^`?X+?m8#EX6+41y5%=_;+j?tYktYLZH0QS$3j$P)sEJ-<$11Jr!wcvsx=p) z(~gD4nqAuK7Bv0rOJBz23k)>lUpw9YuB1rpMy(SO1x`hLWOD;>K|<{(OkCX zy}qt;($0#6YR-D~fK6T*}EZ5<5@*LCPRX}hP?g-llJdKQ-@Ze?k7 zq<{IHdnNj1oX2-Mt$D1z$W6!XcCwW!d$mj9HNBrxQlbtQx-Q?k%duO1Y3gtGjJ0ZS zBl>l2mp)q6WU6QF#~;Ev`;LgexS^obi%DNO&IG9BD8#i)e~`L^DgQue1-o6t`3G!w zTFyTdt6{f4sQ*Fw4`2O(`5(-SBAS*)v?+xpF`Fd`mpw9kb13i4QAL~9{znFFA9T(= zlJThFpe=z-HWesEe!PPt3@*hS2Fx%HI|H%D^VO>-FgXur^MmxqoleMV* zt=(Xs(rG-Kt!DG>hi~L7ejJHC^z-)KZvx_Ccf(_X0z-rEU%Gd7v*DR&w{qh&zHmJY zz5vODIS=>thRiP8_^0lmy;pv)*!E5HD%nru%{Iu3UGn7z^FOb?O)e3eZq;`zJ|H~* z^BVT-zf30==6v?sv@r0)WScI-=PLbMxR_2ZowNI5pP$rS*UqxVcaLX^yfL3xE~gvv z`{k1vwsWs1@ZM|p*POonf%Xr^Acy^;5}!3-!N3M}l3Gjj`aQsWCM zOEU8F;&by;N^?^6!3Ce}+WXPxff%je0~rS?J3+=p`n|PQ1sbOg#HzTAL$O1zpb~C8 z$O<&$y*+({HO`;#IoEOCbB&Mgr3+gg1u--&x-#j~A{$m!`3Vaa3W+lyytE-i1I0@U zxZDsQpP83g5+4t<{x-*yXs?P3=+*~#Gcw6B{85_fEdjPbmNjn8&(`fqu2)y7<^Vie1RF@FkgVI fI)KY6l;8*20t$YNuwrEc1u6>=Rs&rw1?B+&#xxB~ literal 0 HcmV?d00001 diff --git a/integration/resources/code/swagger1.json b/integration/resources/code/swagger1.json new file mode 100644 index 0000000000..81d235a108 --- /dev/null +++ b/integration/resources/code/swagger1.json @@ -0,0 +1,340 @@ +{ + "swagger": "2.0", + "info": { + "title": "PetStore", + "description": "Your first API with Amazon API Gateway. This is a sample API that integrates via HTTP with our demo Pet Store endpoints" + }, + "schemes": [ + "https" + ], + "paths": { + "/": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "text/html" + ], + "responses": { + "200": { + "description": "200 response", + "headers": { + "Content-Type": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Content-Type": "'text/html'" + }, + "responseTemplates": { + "text/html": "\n \n \n \n \n

Welcome to your Pet Store API

\n

\n You have succesfully deployed your first API. You are seeing this HTML page because the GET method to the root resource of your API returns this content as a Mock integration.\n

\n

\n The Pet Store API contains the /pets and /pets/{petId} resources. By making a GET request to /pets you can retrieve a list of Pets in your API. If you are looking for a specific pet, for example the pet with ID 1, you can make a GET request to /pets/1.\n

\n

\n You can use a REST client such as Postman to test the POST methods in your API to create a new pet. Use the sample body below to send the POST request:\n

\n
\n{\n    \"type\" : \"cat\",\n    \"price\" : 123.11\n}\n        
\n \n" + } + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + }, + "post": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "httpMethod": "POST", + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + }, + "Access-Control-Allow-Methods": { + "type": "string" + }, + "Access-Control-Allow-Headers": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'", + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + } + }, + "/pets": { + "get": { + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "type", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "page", + "in": "query", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "httpMethod": "GET", + "requestParameters": { + "integration.request.querystring.page": "method.request.querystring.page", + "integration.request.querystring.type": "method.request.querystring.type" + }, + "type": "http" + } + }, + "post": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "httpMethod": "POST", + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + }, + "Access-Control-Allow-Methods": { + "type": "string" + }, + "Access-Control-Allow-Headers": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'POST,GET,OPTIONS'", + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + } + }, + "/pets/{petId}": { + "get": { + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets/{petId}", + "httpMethod": "GET", + "requestParameters": { + "integration.request.path.petId": "method.request.path.petId" + }, + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + }, + "Access-Control-Allow-Methods": { + "type": "string" + }, + "Access-Control-Allow-Headers": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + } + } + }, + "definitions": { + "Empty": { + "type": "object" + } + } +} diff --git a/integration/resources/code/swagger2.json b/integration/resources/code/swagger2.json new file mode 100644 index 0000000000..7db53a271b --- /dev/null +++ b/integration/resources/code/swagger2.json @@ -0,0 +1,215 @@ +{ + "swagger": "2.0", + "info": { + "title": "PetStore", + "description": "Your first API with Amazon API Gateway. This is a sample API that integrates via HTTP with our demo Pet Store endpoints" + }, + "schemes": [ + "https" + ], + "paths": { + "/": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "text/html" + ], + "responses": { + "200": { + "description": "200 response", + "headers": { + "Content-Type": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Content-Type": "'text/html'" + }, + "responseTemplates": { + "text/html": "\n \n \n \n \n

Welcome to your Pet Store API

\n

\n You have succesfully deployed your first API. You are seeing this HTML page because the GET method to the root resource of your API returns this content as a Mock integration.\n

\n

\n The Pet Store API contains the /pets and /pets/{petId} resources. By making a GET request to /pets you can retrieve a list of Pets in your API. If you are looking for a specific pet, for example the pet with ID 1, you can make a GET request to /pets/1.\n

\n

\n You can use a REST client such as Postman to test the POST methods in your API to create a new pet. Use the sample body below to send the POST request:\n

\n
\n{\n    \"type\" : \"cat\",\n    \"price\" : 123.11\n}\n        
\n \n" + } + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + }, + "post": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets", + "httpMethod": "POST", + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + }, + "Access-Control-Allow-Methods": { + "type": "string" + }, + "Access-Control-Allow-Headers": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'", + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + } + }, + "/pets/{petId}": { + "get": { + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "petId", + "in": "path", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "uri": "http://petstore-demo-endpoint.execute-api.com/petstore/pets/{petId}", + "httpMethod": "GET", + "requestParameters": { + "integration.request.path.petId": "method.request.path.petId" + }, + "type": "http" + } + }, + "options": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/Empty" + }, + "headers": { + "Access-Control-Allow-Origin": { + "type": "string" + }, + "Access-Control-Allow-Methods": { + "type": "string" + }, + "Access-Control-Allow-Headers": { + "type": "string" + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200", + "responseParameters": { + "method.response.header.Access-Control-Allow-Methods": "'GET,OPTIONS'", + "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'", + "method.response.header.Access-Control-Allow-Origin": "'*'" + } + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "type": "mock" + } + } + } + }, + "definitions": { + "Empty": { + "type": "object" + } + } +} diff --git a/integration/resources/code/template.yaml b/integration/resources/code/template.yaml new file mode 100644 index 0000000000..1949e8ba61 --- /dev/null +++ b/integration/resources/code/template.yaml @@ -0,0 +1,8 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: 'AWS::Serverless-2016-10-31' +Resources: + MyTable: + Type: 'AWS::Serverless::SimpleTable' +Outputs: + TableName: + Value: !Ref MyTable \ No newline at end of file diff --git a/integration/resources/expected/single/basic_api.json b/integration/resources/expected/single/basic_api.json new file mode 100644 index 0000000000..d45cb8ce34 --- /dev/null +++ b/integration/resources/expected/single/basic_api.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiMyNewStageNameStage", "ResourceType":"AWS::ApiGateway::Stage" } +] diff --git a/integration/resources/expected/single/basic_api_inline_openapi.json b/integration/resources/expected/single/basic_api_inline_openapi.json new file mode 100644 index 0000000000..d45cb8ce34 --- /dev/null +++ b/integration/resources/expected/single/basic_api_inline_openapi.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiMyNewStageNameStage", "ResourceType":"AWS::ApiGateway::Stage" } +] diff --git a/integration/resources/expected/single/basic_api_inline_swagger.json b/integration/resources/expected/single/basic_api_inline_swagger.json new file mode 100644 index 0000000000..d45cb8ce34 --- /dev/null +++ b/integration/resources/expected/single/basic_api_inline_swagger.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiMyNewStageNameStage", "ResourceType":"AWS::ApiGateway::Stage" } +] diff --git a/integration/resources/expected/single/basic_api_inline_with_cache.json b/integration/resources/expected/single/basic_api_inline_with_cache.json new file mode 100644 index 0000000000..d45cb8ce34 --- /dev/null +++ b/integration/resources/expected/single/basic_api_inline_with_cache.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiMyNewStageNameStage", "ResourceType":"AWS::ApiGateway::Stage" } +] diff --git a/integration/resources/expected/single/basic_api_inline_with_tags.json b/integration/resources/expected/single/basic_api_inline_with_tags.json new file mode 100644 index 0000000000..84d8e643ff --- /dev/null +++ b/integration/resources/expected/single/basic_api_inline_with_tags.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiStage", "ResourceType":"AWS::ApiGateway::Stage" } +] diff --git a/integration/resources/expected/single/basic_api_with_tags.json b/integration/resources/expected/single/basic_api_with_tags.json new file mode 100644 index 0000000000..d636093ca0 --- /dev/null +++ b/integration/resources/expected/single/basic_api_with_tags.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGateway::RestApi" }, + { "LogicalResourceId":"MyApiDeployment", "ResourceType":"AWS::ApiGateway::Deployment" }, + { "LogicalResourceId":"MyApiStage", "ResourceType":"AWS::ApiGateway::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_application_s3_location.json b/integration/resources/expected/single/basic_application_s3_location.json new file mode 100644 index 0000000000..a033c90b76 --- /dev/null +++ b/integration/resources/expected/single/basic_application_s3_location.json @@ -0,0 +1,3 @@ +[ + { "LogicalResourceId":"MyNestedApp", "ResourceType":"AWS::CloudFormation::Stack" } +] diff --git a/integration/resources/expected/single/basic_application_sar_location.json b/integration/resources/expected/single/basic_application_sar_location.json new file mode 100644 index 0000000000..a033c90b76 --- /dev/null +++ b/integration/resources/expected/single/basic_application_sar_location.json @@ -0,0 +1,3 @@ +[ + { "LogicalResourceId":"MyNestedApp", "ResourceType":"AWS::CloudFormation::Stack" } +] diff --git a/integration/resources/expected/single/basic_application_sar_location_with_intrinsics.json b/integration/resources/expected/single/basic_application_sar_location_with_intrinsics.json new file mode 100644 index 0000000000..884f93d12e --- /dev/null +++ b/integration/resources/expected/single/basic_application_sar_location_with_intrinsics.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyNestedApp", "ResourceType":"AWS::CloudFormation::Stack" }, + { "LogicalResourceId":"MySns", "ResourceType":"AWS::SNS::Topic" } +] diff --git a/integration/resources/expected/single/basic_function.json b/integration/resources/expected/single/basic_function.json new file mode 100644 index 0000000000..2cb129f54d --- /dev/null +++ b/integration/resources/expected/single/basic_function.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" } +] diff --git a/integration/resources/expected/single/basic_function_event_destinations.json b/integration/resources/expected/single/basic_function_event_destinations.json new file mode 100644 index 0000000000..3588c9d130 --- /dev/null +++ b/integration/resources/expected/single/basic_function_event_destinations.json @@ -0,0 +1,14 @@ +[ + { "LogicalResourceId":"MyTestFunction2", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyTestFunction2EventInvokeConfigOnSuccessTopic", "ResourceType":"AWS::SNS::Topic" }, + { "LogicalResourceId":"MyTestFunctionRole", "ResourceType":"AWS::IAM::Role"}, + { "LogicalResourceId":"MyTestFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyTestFunction2Aliaslive", "ResourceType":"AWS::Lambda::Alias"}, + { "LogicalResourceId":"MyTestFunctionEventInvokeConfigOnSuccessQueue", "ResourceType":"AWS::SQS::Queue"}, + { "LogicalResourceId":"MyTestFunction2Role", "ResourceType":"AWS::IAM::Role"}, + { "LogicalResourceId":"DestinationLambda", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyTestFunction2EventInvokeConfig", "ResourceType":"AWS::Lambda::EventInvokeConfig"}, + { "LogicalResourceId":"MyTestFunction2Version", "ResourceType":"AWS::Lambda::Version"}, + { "LogicalResourceId":"MyTestFunctionEventInvokeConfig", "ResourceType":"AWS::Lambda::EventInvokeConfig"}, + { "LogicalResourceId":"DestinationLambdaRole", "ResourceType":"AWS::IAM::Role"} +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_function_no_envvar.json b/integration/resources/expected/single/basic_function_no_envvar.json new file mode 100644 index 0000000000..98d62e4bbd --- /dev/null +++ b/integration/resources/expected/single/basic_function_no_envvar.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_function_openapi.json b/integration/resources/expected/single/basic_function_openapi.json new file mode 100644 index 0000000000..98d62e4bbd --- /dev/null +++ b/integration/resources/expected/single/basic_function_openapi.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_function_with_kmskeyarn.json b/integration/resources/expected/single/basic_function_with_kmskeyarn.json new file mode 100644 index 0000000000..5cba0276b4 --- /dev/null +++ b/integration/resources/expected/single/basic_function_with_kmskeyarn.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"BasicFunctionWithKmsKeyArn", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"BasicFunctionWithKmsKeyArnRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyKey", "ResourceType":"AWS::KMS::Key" } +] diff --git a/integration/resources/expected/single/basic_function_with_sns_dlq.json b/integration/resources/expected/single/basic_function_with_sns_dlq.json new file mode 100644 index 0000000000..3ded5dd12c --- /dev/null +++ b/integration/resources/expected/single/basic_function_with_sns_dlq.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyTopic", "ResourceType":"AWS::SNS::Topic" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_function_with_sqs_dlq.json b/integration/resources/expected/single/basic_function_with_sqs_dlq.json new file mode 100644 index 0000000000..a29c8734bc --- /dev/null +++ b/integration/resources/expected/single/basic_function_with_sqs_dlq.json @@ -0,0 +1,5 @@ +[ + { "LogicalResourceId":"MyFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyQueue", "ResourceType":"AWS::SQS::Queue" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_function_with_tags.json b/integration/resources/expected/single/basic_function_with_tags.json new file mode 100644 index 0000000000..4e83eaf6b0 --- /dev/null +++ b/integration/resources/expected/single/basic_function_with_tags.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" } +] diff --git a/integration/resources/expected/single/basic_function_with_tracing.json b/integration/resources/expected/single/basic_function_with_tracing.json new file mode 100644 index 0000000000..e47b43ddd8 --- /dev/null +++ b/integration/resources/expected/single/basic_function_with_tracing.json @@ -0,0 +1,6 @@ +[ + { "LogicalResourceId":"ActiveTracingFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"ActiveTracingFunctionRole", "ResourceType":"AWS::IAM::Role"}, + { "LogicalResourceId":"PassThroughTracingFunction", "ResourceType":"AWS::Lambda::Function"}, + { "LogicalResourceId":"PassThroughTracingFunctionRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_http_api.json b/integration/resources/expected/single/basic_http_api.json new file mode 100644 index 0000000000..7fac895cbd --- /dev/null +++ b/integration/resources/expected/single/basic_http_api.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyApi", "ResourceType":"AWS::ApiGatewayV2::Api"}, + { "LogicalResourceId":"MyApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_layer.json b/integration/resources/expected/single/basic_layer.json new file mode 100644 index 0000000000..beba3153c7 --- /dev/null +++ b/integration/resources/expected/single/basic_layer.json @@ -0,0 +1,3 @@ +[ + { "LogicalResourceId":"MyLayerVersion", "ResourceType":"AWS::Lambda::LayerVersion"} +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_layer_with_parameters.json b/integration/resources/expected/single/basic_layer_with_parameters.json new file mode 100644 index 0000000000..beba3153c7 --- /dev/null +++ b/integration/resources/expected/single/basic_layer_with_parameters.json @@ -0,0 +1,3 @@ +[ + { "LogicalResourceId":"MyLayerVersion", "ResourceType":"AWS::Lambda::LayerVersion"} +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_state_machine_inline_definition.json b/integration/resources/expected/single/basic_state_machine_inline_definition.json new file mode 100644 index 0000000000..4d778a7e46 --- /dev/null +++ b/integration/resources/expected/single/basic_state_machine_inline_definition.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyBasicStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine"}, + { "LogicalResourceId":"MyBasicStateMachineRole", "ResourceType":"AWS::IAM::Role" } +] \ No newline at end of file diff --git a/integration/resources/expected/single/basic_state_machine_with_tags.json b/integration/resources/expected/single/basic_state_machine_with_tags.json new file mode 100644 index 0000000000..976394f6bf --- /dev/null +++ b/integration/resources/expected/single/basic_state_machine_with_tags.json @@ -0,0 +1,4 @@ +[ + { "LogicalResourceId":"MyStateMachine", "ResourceType":"AWS::StepFunctions::StateMachine"}, + { "LogicalResourceId":"MyStateMachineRole", "ResourceType":"AWS::IAM::Role" } +] diff --git a/integration/resources/templates/single/basic_api.yaml b/integration/resources/templates/single/basic_api.yaml new file mode 100644 index 0000000000..0b6322cec0 --- /dev/null +++ b/integration/resources/templates/single/basic_api.yaml @@ -0,0 +1,6 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: MyNewStageName + DefinitionUri: ${definitionuri} diff --git a/integration/resources/templates/single/basic_api_inline_openapi.yaml b/integration/resources/templates/single/basic_api_inline_openapi.yaml new file mode 100644 index 0000000000..7f14ad5dc3 --- /dev/null +++ b/integration/resources/templates/single/basic_api_inline_openapi.yaml @@ -0,0 +1,27 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: MyNewStageName + DefinitionBody: + # Simple HTTP Proxy API + openapi: "3.0" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api" + basePath: "/demo" + schemes: + - "https" + paths: + /http/{proxy+}: + x-amazon-apigateway-any-method: + parameters: + - name: "proxy" + in: "path" + x-amazon-apigateway-integration: + type: "http_proxy" + uri: "http://httpbin.org/{proxy}" + httpMethod: "ANY" + passthroughBehavior: "when_no_match" + requestParameters: + integration.request.path.proxy: "method.request.path.proxy" diff --git a/integration/resources/templates/single/basic_api_inline_swagger.yaml b/integration/resources/templates/single/basic_api_inline_swagger.yaml new file mode 100644 index 0000000000..ce9ff24298 --- /dev/null +++ b/integration/resources/templates/single/basic_api_inline_swagger.yaml @@ -0,0 +1,27 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: MyNewStageName + DefinitionBody: + # Simple HTTP Proxy API + swagger: "2.0" + info: + version: "2016-09-23T22:23:23Z" + title: "Simple Api" + basePath: "/demo" + schemes: + - "https" + paths: + /http/{proxy+}: + x-amazon-apigateway-any-method: + parameters: + - name: "proxy" + in: "path" + x-amazon-apigateway-integration: + type: "http_proxy" + uri: "http://httpbin.org/{proxy}" + httpMethod: "ANY" + passthroughBehavior: "when_no_match" + requestParameters: + integration.request.path.proxy: "method.request.path.proxy" diff --git a/integration/resources/templates/single/basic_api_with_tags.yaml b/integration/resources/templates/single/basic_api_with_tags.yaml new file mode 100644 index 0000000000..c3bce8d8d4 --- /dev/null +++ b/integration/resources/templates/single/basic_api_with_tags.yaml @@ -0,0 +1,9 @@ +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: my-new-stage-name + DefinitionUri: ${definitionuri} + Tags: + TagKey1: TagValue1 + TagKey2: "" diff --git a/integration/resources/templates/single/basic_application_s3_location.yaml b/integration/resources/templates/single/basic_application_s3_location.yaml new file mode 100644 index 0000000000..5a60446747 --- /dev/null +++ b/integration/resources/templates/single/basic_application_s3_location.yaml @@ -0,0 +1,5 @@ +Resources: + MyNestedApp: + Type: AWS::Serverless::Application + Properties: + Location: ${templateurl} diff --git a/integration/resources/templates/single/basic_application_sar_location.yaml b/integration/resources/templates/single/basic_application_sar_location.yaml new file mode 100644 index 0000000000..5bd59c2e49 --- /dev/null +++ b/integration/resources/templates/single/basic_application_sar_location.yaml @@ -0,0 +1,9 @@ +Resources: + MyNestedApp: + Type: AWS::Serverless::Application + Properties: + Location: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python + SemanticVersion: 1.0.2 + Parameters: + IdentityNameParameter: test diff --git a/integration/resources/templates/single/basic_application_sar_location_with_intrinsics.yaml b/integration/resources/templates/single/basic_application_sar_location_with_intrinsics.yaml new file mode 100644 index 0000000000..471af7ae00 --- /dev/null +++ b/integration/resources/templates/single/basic_application_sar_location_with_intrinsics.yaml @@ -0,0 +1,57 @@ +Parameters: + SemanticVersion: + Type: String + Default: 1.0.2 + +Mappings: + SARApplication: + us-east-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python + us-east-2: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + us-west-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + us-west-2: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + eu-central-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + eu-west-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + eu-west-2: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + eu-west-3: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + ap-south-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + ap-northeast-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + ap-northeast-2: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + ap-southeast-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + ap-southeast-2: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + ca-central-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + sa-east-1: + ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world-python3 + +Resources: + MyNestedApp: + Type: AWS::Serverless::Application + Properties: + Location: + ApplicationId: + Fn::FindInMap: + - SARApplication + - {Ref: 'AWS::Region'} + - ApplicationId + SemanticVersion: + Ref: SemanticVersion + Parameters: + IdentityNameParameter: test + NotificationARNs: + - Ref: MySns + + MySns: + Type: AWS::SNS::Topic \ No newline at end of file diff --git a/integration/resources/templates/single/basic_function.yaml b/integration/resources/templates/single/basic_function.yaml new file mode 100644 index 0000000000..d3fdf30784 --- /dev/null +++ b/integration/resources/templates/single/basic_function.yaml @@ -0,0 +1,15 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Policies: + - AWSLambdaRole + - AmazonS3ReadOnlyAccess + Environment: + Variables: + Name: Value + Name2: Value2 diff --git a/integration/resources/templates/single/basic_function_event_destinations.yaml b/integration/resources/templates/single/basic_function_event_destinations.yaml new file mode 100644 index 0000000000..2129313b1b --- /dev/null +++ b/integration/resources/templates/single/basic_function_event_destinations.yaml @@ -0,0 +1,95 @@ +Conditions: + QueueCreationDisabled: + Fn::Equals: + - false + - true +Resources: + MyTestFunction: + Type: AWS::Serverless::Function + Properties: + EventInvokeConfig: + MaximumEventAgeInSeconds: 70 + MaximumRetryAttempts: 1 + DestinationConfig: + OnSuccess: + Type: SQS + Destination: + Fn::If: + - QueueCreationDisabled + - Fn::GetAtt: + - DestinationSQS + - Arn + - Ref: 'AWS::NoValue' + OnFailure: + Type: Lambda + Destination: + Fn::GetAtt: + - DestinationLambda + - Arn + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + MyTestFunction2: + Type: AWS::Serverless::Function + Properties: + AutoPublishAlias: live + EventInvokeConfig: + MaximumEventAgeInSeconds: 80 + MaximumRetryAttempts: 2 + DestinationConfig: + OnSuccess: + Type: SNS + OnFailure: + Type: EventBridge + Destination: + Fn::Sub: arn:${AWS::Partition}:events:${AWS::Region}:${AWS::AccountId}:event-bus/default + InlineCode: | + exports.handler = function(event, context, callback) { + var event_received_at = new Date().toISOString(); + console.log('Event received at: ' + event_received_at); + console.log('Received event:', JSON.stringify(event, null, 2)); + if (event.Success) { + console.log("Success"); + context.callbackWaitsForEmptyEventLoop = false; + callback(null); + } else { + console.log("Failure"); + context.callbackWaitsForEmptyEventLoop = false; + callback(new Error("Failure from event, Success = false, I am failing!"), 'Destination Function Error Thrown'); + } + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + DestinationLambda: + Type: AWS::Serverless::Function + Properties: + InlineCode: | + exports.handler = async (event) => { + const response = { + statusCode: 200, + body: JSON.stringify('Hello from Lambda!'), + }; + return response; + }; + Handler: index.handler + Runtime: nodejs10.x + MemorySize: 1024 + DestinationSQS: + Condition: QueueCreationDisabled + Type: AWS::SQS::Queue diff --git a/integration/resources/templates/single/basic_function_no_envvar.yaml b/integration/resources/templates/single/basic_function_no_envvar.yaml new file mode 100644 index 0000000000..6ecedfd923 --- /dev/null +++ b/integration/resources/templates/single/basic_function_no_envvar.yaml @@ -0,0 +1,11 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Policies: + - AWSLambdaRole + - AmazonS3ReadOnlyAccess diff --git a/integration/resources/templates/single/basic_function_openapi.yaml b/integration/resources/templates/single/basic_function_openapi.yaml new file mode 100644 index 0000000000..359f7d58bc --- /dev/null +++ b/integration/resources/templates/single/basic_function_openapi.yaml @@ -0,0 +1,18 @@ +Globals: + Api: + OpenApiVersion: 3.0.1 +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Policies: + - AWSLambdaRole + - AmazonS3ReadOnlyAccess + Environment: + Variables: + Name: Value + Name2: Value2 diff --git a/integration/resources/templates/single/basic_function_with_kmskeyarn.yaml b/integration/resources/templates/single/basic_function_with_kmskeyarn.yaml new file mode 100644 index 0000000000..91a9fc05ff --- /dev/null +++ b/integration/resources/templates/single/basic_function_with_kmskeyarn.yaml @@ -0,0 +1,32 @@ +Resources: + BasicFunctionWithKmsKeyArn: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Environment: + Variables: + Key: Value + KmsKeyArn: + Fn::GetAtt: [MyKey, Arn] + + + MyKey: + Type: "AWS::KMS::Key" + Properties: + Description: "A sample key" + KeyPolicy: + Version: "2012-10-17" + Id: "key-default-1" + Statement: + - + Sid: "Allow administration of the key" + Effect: "Allow" + Principal: + AWS: + Fn::Sub: "arn:${AWS::Partition}:iam::${AWS::AccountId}:root" + Action: + - "kms:*" + Resource: "*" \ No newline at end of file diff --git a/integration/resources/templates/single/basic_function_with_sns_dlq.yaml b/integration/resources/templates/single/basic_function_with_sns_dlq.yaml new file mode 100644 index 0000000000..0d4435fef0 --- /dev/null +++ b/integration/resources/templates/single/basic_function_with_sns_dlq.yaml @@ -0,0 +1,14 @@ +Resources: + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + DeadLetterQueue: + Type: SNS + TargetArn: + Ref: "MyTopic" + + MyTopic: + Type: AWS::SNS::Topic diff --git a/integration/resources/templates/single/basic_function_with_sqs_dlq.yaml b/integration/resources/templates/single/basic_function_with_sqs_dlq.yaml new file mode 100644 index 0000000000..26aa16b01a --- /dev/null +++ b/integration/resources/templates/single/basic_function_with_sqs_dlq.yaml @@ -0,0 +1,15 @@ + +Resources: + MyFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + DeadLetterQueue: + Type: SQS + TargetArn: + Fn::GetAtt: ["MyQueue", "Arn"] + + MyQueue: + Type: AWS::SQS::Queue diff --git a/integration/resources/templates/single/basic_function_with_tags.yaml b/integration/resources/templates/single/basic_function_with_tags.yaml new file mode 100644 index 0000000000..cc815a52e8 --- /dev/null +++ b/integration/resources/templates/single/basic_function_with_tags.yaml @@ -0,0 +1,14 @@ +Resources: + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Policies: + - AWSLambdaRole + - AmazonS3ReadOnlyAccess + Tags: + TagKey1: TagValue1 + TagKey2: "" diff --git a/integration/resources/templates/single/basic_function_with_tracing.yaml b/integration/resources/templates/single/basic_function_with_tracing.yaml new file mode 100644 index 0000000000..f9fa0809cd --- /dev/null +++ b/integration/resources/templates/single/basic_function_with_tracing.yaml @@ -0,0 +1,36 @@ +Parameters: + Bucket: + Type: String + CodeKey: + Type: String + SwaggerKey: + Type: String + TracingParamPassThrough: + Type: String + Default: PassThrough + +Resources: + ActiveTracingFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Policies: + - AWSLambdaRole + - AmazonS3ReadOnlyAccess + Tracing: Active + + PassThroughTracingFunction: + Type: AWS::Serverless::Function + Properties: + Handler: index.handler + Runtime: nodejs12.x + CodeUri: ${codeuri} + MemorySize: 128 + Policies: + - AWSLambdaRole + - AmazonS3ReadOnlyAccess + Tracing: + Ref: TracingParamPassThrough diff --git a/integration/resources/templates/single/basic_http_api.yaml b/integration/resources/templates/single/basic_http_api.yaml new file mode 100644 index 0000000000..27e3c340d2 --- /dev/null +++ b/integration/resources/templates/single/basic_http_api.yaml @@ -0,0 +1,11 @@ +Resources: + MyApi: + Type: AWS::Serverless::HttpApi + Properties: + DefinitionBody: + info: + version: '1.0' + title: + Ref: AWS::StackName + openapi: 3.0.1 + paths: {} \ No newline at end of file diff --git a/integration/resources/templates/single/basic_layer.yaml b/integration/resources/templates/single/basic_layer.yaml new file mode 100644 index 0000000000..6b308fd91f --- /dev/null +++ b/integration/resources/templates/single/basic_layer.yaml @@ -0,0 +1,6 @@ +Resources: + MyLayerVersion: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: ${contenturi} + RetentionPolicy: Delete diff --git a/integration/resources/templates/single/basic_layer_with_parameters.yaml b/integration/resources/templates/single/basic_layer_with_parameters.yaml new file mode 100644 index 0000000000..f5eb8fdb84 --- /dev/null +++ b/integration/resources/templates/single/basic_layer_with_parameters.yaml @@ -0,0 +1,46 @@ +Parameters: + Retention: + Type: String + Default: Retain + License: + Type: String + Default: MIT-0 + Runtimes: + Type: CommaDelimitedList + Default: nodejs12.x,nodejs10.x + LayerName: + Type: String + Default: MyNamedLayerVersion + Description: + Type: String + Default: Some description about this layer goes here + +Resources: + MyLayerVersion: + Type: AWS::Serverless::LayerVersion + Properties: + ContentUri: ${contenturi} + LayerName: + Ref: LayerName + RetentionPolicy: + Ref: Retention + CompatibleRuntimes: + Ref: Runtimes + LicenseInfo: + Ref: License + Description: + Ref: Description + +Outputs: + MyLayerArn: + Value: + Ref: MyLayerVersion + License: + Value: + Ref: License + Description: + Value: + Ref: Description + LayerName: + Value: + Ref: LayerName \ No newline at end of file diff --git a/integration/resources/templates/single/basic_state_machine_inline_definition.yaml b/integration/resources/templates/single/basic_state_machine_inline_definition.yaml new file mode 100644 index 0000000000..8fb8a16cde --- /dev/null +++ b/integration/resources/templates/single/basic_state_machine_inline_definition.yaml @@ -0,0 +1,23 @@ +Resources: + MyBasicStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Type: STANDARD + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: '2012-10-17' + Statement: + - Effect: Deny + Action: "*" + Resource: "*" diff --git a/integration/resources/templates/single/basic_state_machine_with_tags.yaml b/integration/resources/templates/single/basic_state_machine_with_tags.yaml new file mode 100644 index 0000000000..8156526419 --- /dev/null +++ b/integration/resources/templates/single/basic_state_machine_with_tags.yaml @@ -0,0 +1,33 @@ +Resources: + MyStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Definition: + Comment: A Hello World example of the Amazon States Language using Pass states + StartAt: Hello + States: + Hello: + Type: Pass + Result: Hello + Next: World + World: + Type: Pass + Result: World + End: true + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Deny + Action: "*" + Resource: "*" + Tags: + TagOne: ValueOne + TagTwo: ValueTwo + Tracing: + Enabled: true + +Outputs: + MyStateMachineArn: + Description: ARN of the state machine + Value: + Ref: MyStateMachine diff --git a/integration/resources/templates/single/basic_table_no_param.yaml b/integration/resources/templates/single/basic_table_no_param.yaml new file mode 100644 index 0000000000..ae4fe8164d --- /dev/null +++ b/integration/resources/templates/single/basic_table_no_param.yaml @@ -0,0 +1,4 @@ +Resources: + MyApi: + Type: AWS::Serverless::SimpleTable + # SimpleTable does NOT require any parameters diff --git a/integration/resources/templates/single/basic_table_with_param.yaml b/integration/resources/templates/single/basic_table_with_param.yaml new file mode 100644 index 0000000000..c0a7039c3b --- /dev/null +++ b/integration/resources/templates/single/basic_table_with_param.yaml @@ -0,0 +1,12 @@ +Resources: + MyApi: + Type: AWS::Serverless::SimpleTable + Properties: + + PrimaryKey: + Name: mynewid + Type: Number + + ProvisionedThroughput: + ReadCapacityUnits: 2 + WriteCapacityUnits: 2 diff --git a/integration/single/__init__.py b/integration/single/__init__.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/integration/single/__init__.py @@ -0,0 +1 @@ + diff --git a/integration/single/test_basic_api.py b/integration/single/test_basic_api.py new file mode 100644 index 0000000000..5e308d40da --- /dev/null +++ b/integration/single/test_basic_api.py @@ -0,0 +1,79 @@ +from integration.helpers.base_test import BaseTest + + +class TestBasicApi(BaseTest): + """ + Basic AWS::Serverless::Api tests + """ + + def test_basic_api(self): + """ + Creates an API and updates its DefinitionUri + """ + self.create_and_verify_stack("basic_api") + + first_dep_ids = self.get_stack_deployment_ids() + self.assertEqual(len(first_dep_ids), 1) + + self.set_template_resource_property("MyApi", "DefinitionUri", self.get_s3_uri("swagger2.json")) + self.transform_template() + self.deploy_stack() + + second_dep_ids = self.get_stack_deployment_ids() + self.assertEqual(len(second_dep_ids), 1) + + self.assertEqual(len(set(first_dep_ids).intersection(second_dep_ids)), 0) + + def test_basic_api_inline_openapi(self): + """ + Creates an API with and inline OpenAPI and updates its DefinitionBody basePath + """ + self.create_and_verify_stack("basic_api_inline_openapi") + + first_dep_ids = self.get_stack_deployment_ids() + self.assertEqual(len(first_dep_ids), 1) + + body = self.get_template_resource_property("MyApi", "DefinitionBody") + body["basePath"] = "/newDemo" + self.set_template_resource_property("MyApi", "DefinitionBody", body) + self.transform_template() + self.deploy_stack() + + second_dep_ids = self.get_stack_deployment_ids() + self.assertEqual(len(second_dep_ids), 1) + + self.assertEqual(len(set(first_dep_ids).intersection(second_dep_ids)), 0) + + def test_basic_api_inline_swagger(self): + """ + Creates an API with an inline Swagger and updates its DefinitionBody basePath + """ + self.create_and_verify_stack("basic_api_inline_swagger") + + first_dep_ids = self.get_stack_deployment_ids() + self.assertEqual(len(first_dep_ids), 1) + + body = self.get_template_resource_property("MyApi", "DefinitionBody") + body["basePath"] = "/newDemo" + self.set_template_resource_property("MyApi", "DefinitionBody", body) + self.transform_template() + self.deploy_stack() + + second_dep_ids = self.get_stack_deployment_ids() + self.assertEqual(len(second_dep_ids), 1) + + self.assertEqual(len(set(first_dep_ids).intersection(second_dep_ids)), 0) + + def test_basic_api_with_tags(self): + """ + Creates an API with tags + """ + self.create_and_verify_stack("basic_api_with_tags") + + stages = self.get_api_stack_stages() + self.assertEqual(len(stages), 2) + + stage = next((s for s in stages if s["stageName"] == "my-new-stage-name")) + self.assertIsNotNone(stage) + self.assertEqual(stage["tags"]["TagKey1"], "TagValue1") + self.assertEqual(stage["tags"]["TagKey2"], "") diff --git a/integration/single/test_basic_application.py b/integration/single/test_basic_application.py new file mode 100644 index 0000000000..f7b16c8e02 --- /dev/null +++ b/integration/single/test_basic_application.py @@ -0,0 +1,45 @@ +from integration.helpers.base_test import BaseTest + + +class TestBasicApplication(BaseTest): + """ + Basic AWS::Serverless::Application tests + """ + + def test_basic_application_s3_location(self): + """ + Creates an application with its properties defined as a template + file in a S3 bucket + """ + self.create_and_verify_stack("basic_application_s3_location") + + nested_stack_resource = self.get_stack_nested_stack_resources() + tables = self.get_stack_resources("AWS::DynamoDB::Table", nested_stack_resource) + + self.assertEqual(len(tables), 1) + self.assertEqual(tables[0]["LogicalResourceId"], "MyTable") + + def test_basic_application_sar_location(self): + """ + Creates an application with a lamda function + """ + self.create_and_verify_stack("basic_application_sar_location") + + nested_stack_resource = self.get_stack_nested_stack_resources() + functions = self.get_stack_resources("AWS::Lambda::Function", nested_stack_resource) + + self.assertEqual(len(functions), 1) + self.assertEqual(functions[0]["LogicalResourceId"], "helloworldpython") + + def test_basic_application_sar_location_with_intrinsics(self): + """ + Creates an application with a lambda function with intrinsics + """ + expected_function_name = "helloworldpython" if self.get_region() == "us-east-1" else "helloworldpython3" + self.create_and_verify_stack("basic_application_sar_location_with_intrinsics") + + nested_stack_resource = self.get_stack_nested_stack_resources() + functions = self.get_stack_resources("AWS::Lambda::Function", nested_stack_resource) + + self.assertEqual(len(functions), 1) + self.assertEqual(functions[0]["LogicalResourceId"], expected_function_name) diff --git a/integration/single/test_basic_function.py b/integration/single/test_basic_function.py new file mode 100644 index 0000000000..3763596473 --- /dev/null +++ b/integration/single/test_basic_function.py @@ -0,0 +1,181 @@ +from parameterized import parameterized +from integration.helpers.base_test import BaseTest + + +class TestBasicFunction(BaseTest): + """ + Basic AWS::Lambda::Function tests + """ + + @parameterized.expand( + [ + "basic_function", + "basic_function_no_envvar", + "basic_function_openapi", + ] + ) + def test_basic_function(self, file_name): + """ + Creates a basic lambda function + """ + self.create_and_verify_stack(file_name) + + self.set_template_resource_property("MyLambdaFunction", "Timeout", 10) + self.transform_template() + self.deploy_stack() + + self.assertEqual(self.get_resource_status_by_logical_id("MyLambdaFunction"), "UPDATE_COMPLETE") + + @parameterized.expand( + [ + ("basic_function_with_sns_dlq", "sns:Publish"), + ("basic_function_with_sqs_dlq", "sqs:SendMessage"), + ] + ) + def test_basic_function_with_dlq(self, file_name, action): + """ + Creates a basic lambda function with dead letter queue policy + """ + dlq_policy_name = "DeadLetterQueuePolicy" + self.create_and_verify_stack(file_name) + + lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + function_configuration = self.client_provider.lambda_client.get_function_configuration( + FunctionName=lambda_function_name + ) + dlq_arn = function_configuration["DeadLetterConfig"]["TargetArn"] + self.assertIsNotNone(dlq_arn, "DLQ Arn should be set") + + role_name = self.get_physical_id_by_type("AWS::IAM::Role") + role_policy_result = self.client_provider.iam_client.get_role_policy( + RoleName=role_name, PolicyName=dlq_policy_name + ) + statements = role_policy_result["PolicyDocument"]["Statement"] + + self.assertEqual(len(statements), 1, "Only one statement must be in policy") + self.assertEqual(statements[0]["Action"], action) + self.assertEqual(statements[0]["Resource"], dlq_arn) + self.assertEqual(statements[0]["Effect"], "Allow") + + def test_basic_function_with_kms_key_arn(self): + """ + Creates a basic lambda function with KMS key arn + """ + self.create_and_verify_stack("basic_function_with_kmskeyarn") + + lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + function_configuration = self.client_provider.lambda_client.get_function_configuration( + FunctionName=lambda_function_name + ) + kms_key_arn = function_configuration["KMSKeyArn"] + + self.assertIsNotNone(kms_key_arn, "Expecting KmsKeyArn to be set.") + + def test_basic_function_with_tags(self): + """ + Creates a basic lambda function with tags + """ + self.create_and_verify_stack("basic_function_with_tags") + lambda_function_name = self.get_physical_id_by_type("AWS::Lambda::Function") + get_function_result = self.client_provider.lambda_client.get_function(FunctionName=lambda_function_name) + tags = get_function_result["Tags"] + + self.assertIsNotNone(tags, "Expecting tags on function.") + self.assertTrue("lambda:createdBy" in tags, "Expected 'lambda:CreatedBy' tag key, but not found.") + self.assertEqual("SAM", tags["lambda:createdBy"], "Expected 'SAM' tag value, but not found.") + self.assertTrue("TagKey1" in tags) + self.assertEqual(tags["TagKey1"], "TagValue1") + self.assertTrue("TagKey2" in tags) + self.assertEqual(tags["TagKey2"], "") + + def test_basic_function_event_destinations(self): + """ + Creates a basic lambda function with event destinations + """ + self.create_and_verify_stack("basic_function_event_destinations") + + test_function_1 = self.get_physical_id_by_logical_id("MyTestFunction") + test_function_2 = self.get_physical_id_by_logical_id("MyTestFunction2") + + function_invoke_config_result = self.client_provider.lambda_client.get_function_event_invoke_config( + FunctionName=test_function_1, Qualifier="$LATEST" + ) + self.assertIsNotNone( + function_invoke_config_result["DestinationConfig"], "Expecting destination config to be set." + ) + self.assertEqual( + int(function_invoke_config_result["MaximumEventAgeInSeconds"]), + 70, + "MaximumEventAgeInSeconds value is not set or incorrect.", + ) + self.assertEqual( + int(function_invoke_config_result["MaximumRetryAttempts"]), + 1, + "MaximumRetryAttempts value is not set or incorrect.", + ) + + function_invoke_config_result = self.client_provider.lambda_client.get_function_event_invoke_config( + FunctionName=test_function_2, Qualifier="live" + ) + self.assertIsNotNone( + function_invoke_config_result["DestinationConfig"], "Expecting destination config to be set." + ) + self.assertEqual( + int(function_invoke_config_result["MaximumEventAgeInSeconds"]), + 80, + "MaximumEventAgeInSeconds value is not set or incorrect.", + ) + self.assertEqual( + int(function_invoke_config_result["MaximumRetryAttempts"]), + 2, + "MaximumRetryAttempts value is not set or incorrect.", + ) + + def test_basic_function_with_tracing(self): + """ + Creates a basic lambda function with tracing + """ + parameters = [ + { + "ParameterKey": "Bucket", + "ParameterValue": self.s3_bucket_name, + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + { + "ParameterKey": "CodeKey", + "ParameterValue": "code.zip", + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + { + "ParameterKey": "SwaggerKey", + "ParameterValue": "swagger1.json", + "UsePreviousValue": False, + "ResolvedValue": "string", + }, + ] + self.create_and_verify_stack("basic_function_with_tracing", parameters) + + active_tracing_function_id = self.get_physical_id_by_logical_id("ActiveTracingFunction") + pass_through_tracing_function_id = self.get_physical_id_by_logical_id("PassThroughTracingFunction") + + function_configuration_result = self.client_provider.lambda_client.get_function_configuration( + FunctionName=active_tracing_function_id + ) + self.assertIsNotNone(function_configuration_result["TracingConfig"], "Expecting tracing config to be set.") + self.assertEqual( + function_configuration_result["TracingConfig"]["Mode"], + "Active", + "Expecting tracing config mode to be set to Active.", + ) + + function_configuration_result = self.client_provider.lambda_client.get_function_configuration( + FunctionName=pass_through_tracing_function_id + ) + self.assertIsNotNone(function_configuration_result["TracingConfig"], "Expecting tracing config to be set.") + self.assertEqual( + function_configuration_result["TracingConfig"]["Mode"], + "PassThrough", + "Expecting tracing config mode to be set to PassThrough.", + ) diff --git a/integration/single/test_basic_http_api.py b/integration/single/test_basic_http_api.py new file mode 100644 index 0000000000..62e867b888 --- /dev/null +++ b/integration/single/test_basic_http_api.py @@ -0,0 +1,18 @@ +from integration.helpers.base_test import BaseTest + + +class TestBasicHttpApi(BaseTest): + """ + Basic AWS::Serverless::HttpApi tests + """ + + def test_basic_http_api(self): + """ + Creates a HTTP API + """ + self.create_and_verify_stack("basic_http_api") + + stages = self.get_api_v2_stack_stages() + + self.assertEqual(len(stages), 1) + self.assertEqual(stages[0]["StageName"], "$default") diff --git a/integration/single/test_basic_layer_version.py b/integration/single/test_basic_layer_version.py new file mode 100644 index 0000000000..db672d89f5 --- /dev/null +++ b/integration/single/test_basic_layer_version.py @@ -0,0 +1,43 @@ +from integration.helpers.base_test import BaseTest + + +class TestBasicLayerVersion(BaseTest): + """ + Basic AWS::Lambda::LayerVersion tests + """ + + def test_basic_layer_version(self): + """ + Creates a basic lambda layer version + """ + self.create_and_verify_stack("basic_layer") + + layer_logical_id_1 = self.get_logical_id_by_type("AWS::Lambda::LayerVersion") + + self.set_template_resource_property("MyLayerVersion", "Description", "A basic layer") + self.transform_template() + self.deploy_stack() + + layer_logical_id_2 = self.get_logical_id_by_type("AWS::Lambda::LayerVersion") + + self.assertFalse(layer_logical_id_1 == layer_logical_id_2) + + def test_basic_layer_with_parameters(self): + """ + Creates a basic lambda layer version with parameters + """ + self.create_and_verify_stack("basic_layer_with_parameters") + + outputs = self.get_stack_outputs() + layer_arn = outputs["MyLayerArn"] + license = outputs["License"] + layer_name = outputs["LayerName"] + description = outputs["Description"] + + layer_version_result = self.client_provider.lambda_client.get_layer_version_by_arn(Arn=layer_arn) + self.client_provider.lambda_client.delete_layer_version( + LayerName=layer_name, VersionNumber=layer_version_result["Version"] + ) + + self.assertEqual(layer_version_result["LicenseInfo"], license) + self.assertEqual(layer_version_result["Description"], description) diff --git a/integration/single/test_basic_state_machine.py b/integration/single/test_basic_state_machine.py new file mode 100644 index 0000000000..7902a43190 --- /dev/null +++ b/integration/single/test_basic_state_machine.py @@ -0,0 +1,43 @@ +from integration.helpers.base_test import BaseTest + + +class TestBasicLayerVersion(BaseTest): + """ + Basic AWS::Serverless::StateMachine tests + """ + + def test_basic_state_machine_inline_definition(self): + """ + Creates a State Machine from inline definition + """ + self.create_and_verify_stack("basic_state_machine_inline_definition") + + def test_basic_state_machine_with_tags(self): + """ + Creates a State Machine with tags + """ + self.create_and_verify_stack("basic_state_machine_with_tags") + + tags = self.get_stack_tags("MyStateMachineArn") + + self.assertIsNotNone(tags) + self._verify_tag_presence(tags, "stateMachine:createdBy", "SAM") + self._verify_tag_presence(tags, "TagOne", "ValueOne") + self._verify_tag_presence(tags, "TagTwo", "ValueTwo") + + def _verify_tag_presence(self, tags, key, value): + """ + Verifies the presence of a tag and its value + + Parameters + ---------- + tags : List of dict + List of tag objects + key : string + Tag key + value : string + Tag value + """ + tag = next(tag for tag in tags if tag["key"] == key) + self.assertIsNotNone(tag) + self.assertEqual(tag["value"], value) diff --git a/requirements/dev.txt b/requirements/dev.txt index a08e446e0d..be38513017 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -11,6 +11,11 @@ pytest~=4.6.11; python_version < '3.6' # pytest dropped python 2 support after 4 mock>=3.0.5,<4.0.0 # 4.0.0 drops Python 2 support parameterized~=0.7.4 +# Integration tests +pathlib2>=2.3.5; python_version < '3' +click~=7.1 +dateparser~=0.7 + # Requirements for examples requests~=2.24.0 From 2c77424c2218b850afd7193dbe6f3b550fa377bf Mon Sep 17 00:00:00 2001 From: Alex Wood Date: Mon, 25 Jan 2021 13:46:21 -0800 Subject: [PATCH 20/31] fix: Better Error Handling of Intrinsics (#1896) * Better Error Handling of Intrinsics In HTTP API authorizers, we don't yet handle intrinsics, and an attempt to use them creates a crash when it tries to hash the input `dict`. This change starts by wrapping that use case properly so that a helpful error is raised until intrinsics are supported here. Note: Does not yet handle `!Sub`, looking at options for that. * Add "NoValue" Test Case Should not raise, making that explicit. --- samtranslator/model/api/http_api_generator.py | 11 ++++++++++- tests/model/api/test_http_api_generator.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/samtranslator/model/api/http_api_generator.py b/samtranslator/model/api/http_api_generator.py index d9e07d5d67..e3efc07c78 100644 --- a/samtranslator/model/api/http_api_generator.py +++ b/samtranslator/model/api/http_api_generator.py @@ -14,7 +14,7 @@ from samtranslator.open_api.open_api import OpenApiEditor from samtranslator.translator import logical_id_generator from samtranslator.model.tags.resource_tagging import get_tag_list -from samtranslator.model.intrinsics import is_intrinsic +from samtranslator.model.intrinsics import is_intrinsic, is_intrinsic_no_value from samtranslator.model.route53 import Route53RecordSetGroup _CORS_WILDCARD = "*" @@ -467,6 +467,15 @@ def _set_default_authorizer(self, open_api_editor, authorizers, default_authoriz if not default_authorizer: return + if is_intrinsic_no_value(default_authorizer): + return + + if is_intrinsic(default_authorizer): + raise InvalidResourceException( + self.logical_id, + "Unable to set DefaultAuthorizer because intrinsic functions are not supported for this field.", + ) + if not authorizers.get(default_authorizer): raise InvalidResourceException( self.logical_id, diff --git a/tests/model/api/test_http_api_generator.py b/tests/model/api/test_http_api_generator.py index 404dd7f41b..2a9f4e97a1 100644 --- a/tests/model/api/test_http_api_generator.py +++ b/tests/model/api/test_http_api_generator.py @@ -69,6 +69,19 @@ def test_auth_missing_default_auth(self): with pytest.raises(InvalidResourceException): HttpApiGenerator(**self.kwargs)._construct_http_api() + def test_auth_intrinsic_default_auth(self): + self.kwargs["auth"] = self.authorizers + self.kwargs["auth"]["DefaultAuthorizer"] = {"Ref": "SomeValue"} + self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() + with pytest.raises(InvalidResourceException): + HttpApiGenerator(**self.kwargs)._construct_http_api() + + def test_auth_novalue_default_does_not_raise(self): + self.kwargs["auth"] = self.authorizers + self.kwargs["auth"]["DefaultAuthorizer"] = {"Ref": "AWS::NoValue"} + self.kwargs["definition_body"] = OpenApiEditor.gen_skeleton() + HttpApiGenerator(**self.kwargs)._construct_http_api() + def test_def_uri_invalid_dict(self): self.kwargs["auth"] = None self.kwargs["definition_body"] = None From 4cdc26540be19bbf29a4296a0b08f50f39930783 Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Fri, 29 Jan 2021 12:13:55 -0800 Subject: [PATCH 21/31] Add integration test --- .../function_alias_with_http_api_events.json | 10 ++++++++ .../function_alias_with_http_api_events.yaml | 23 +++++++++++++++++++ integration/single/test_basic_function.py | 1 + 3 files changed, 34 insertions(+) create mode 100644 integration/resources/expected/single/function_alias_with_http_api_events.json create mode 100644 integration/resources/templates/single/function_alias_with_http_api_events.yaml diff --git a/integration/resources/expected/single/function_alias_with_http_api_events.json b/integration/resources/expected/single/function_alias_with_http_api_events.json new file mode 100644 index 0000000000..f107140e7f --- /dev/null +++ b/integration/resources/expected/single/function_alias_with_http_api_events.json @@ -0,0 +1,10 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionAliaslive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"MyLambdaFunctionFooEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"MyLambdaFunctionBarEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"HttpApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"HttpApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] diff --git a/integration/resources/templates/single/function_alias_with_http_api_events.yaml b/integration/resources/templates/single/function_alias_with_http_api_events.yaml new file mode 100644 index 0000000000..eb978eedf1 --- /dev/null +++ b/integration/resources/templates/single/function_alias_with_http_api_events.yaml @@ -0,0 +1,23 @@ +Resources: + HttpApi: + Type: AWS::Serverless::HttpApi + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + AutoPublishAlias: live + Handler: index.handler + Runtime: python3.8 + CodeUri: ${codeuri} + Events: + FooEvent: + Type: HttpApi + Properties: + ApiId: + Ref: HttpApi + BarEvent: + Type: HttpApi + Properties: + ApiId: + Ref: HttpApi + Path: /bar + Method: ANY \ No newline at end of file diff --git a/integration/single/test_basic_function.py b/integration/single/test_basic_function.py index 3763596473..7367683b3f 100644 --- a/integration/single/test_basic_function.py +++ b/integration/single/test_basic_function.py @@ -12,6 +12,7 @@ class TestBasicFunction(BaseTest): "basic_function", "basic_function_no_envvar", "basic_function_openapi", + "function_alias_with_http_api_events", ] ) def test_basic_function(self, file_name): From 8c19a09cffdee6eee1a6f7b441af832b49275c0a Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Fri, 29 Jan 2021 13:32:32 -0800 Subject: [PATCH 22/31] Add trailing newline --- .../templates/single/function_alias_with_http_api_events.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/resources/templates/single/function_alias_with_http_api_events.yaml b/integration/resources/templates/single/function_alias_with_http_api_events.yaml index eb978eedf1..354290bdbb 100644 --- a/integration/resources/templates/single/function_alias_with_http_api_events.yaml +++ b/integration/resources/templates/single/function_alias_with_http_api_events.yaml @@ -20,4 +20,4 @@ Resources: ApiId: Ref: HttpApi Path: /bar - Method: ANY \ No newline at end of file + Method: ANY From 0848e38efc1353ccb9a196cd6093eb522324c54c Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Fri, 29 Jan 2021 13:58:30 -0800 Subject: [PATCH 23/31] Update comment --- samtranslator/open_api/open_api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index b58801073d..43e5d96341 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -103,10 +103,11 @@ def get_integration_function_logical_id(self, path_name, method_name): # Extract lambda integration (${LambdaName.Arn}) and split ".Arn" off from it regex = "([A-Za-z0-9]+\.Arn)" matches = re.findall(regex, arn) - # For Functions with AutoPublishAlias the integration URI will contain - # ${LambdaName}, not ${LambdaName.Arn} (both resolve to the AWS::Lambda::Alias ARN). - # Without the .Arn suffix, parsing the URI is more ambiguous, so instead of - # introducing more brittle hacks, return gracefully. + # For AWS::Serverless::Functions with AutoPublishAlias, the integration URI will + # contain ${LogicalId} (where LogicalId is the logical ID of a AWS::Lambda::Alias), + # not ${LogicalId.Arn} (where LogicalId is the logical ID of a AWS::Lambda::Function). + # Without the .Arn suffix, parsing the URI is even more ambiguous, so instead of + # introducing more hacks, we return gracefully to prevent IndexErrors. if not matches: return False match = matches[0].split(".Arn")[0] From 1a0c464d16cc4c1c5bb1eb815b1f21a768feb5df Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Mon, 1 Feb 2021 14:07:07 -0800 Subject: [PATCH 24/31] Add UT --- tests/openapi/test_openapi.py | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index 9938cab1c7..b398844977 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -437,3 +437,49 @@ def test_must_not_add_description_if_already_defined(self): editor = OpenApiEditor(self.original_openapi_with_description) editor.add_description("New Description") self.assertEqual(editor.openapi["info"]["description"], "Existing Description") + +class TestOpenApiEditor_get_integration_function_of_alias(TestCase): + def setUp(self): + + self.original_openapi = { + "openapi": "3.0.1", + "paths": { + "$default": { + "x-amazon-apigateway-any-method": { + "Fn::If": [ + "condition", + { + "security": [{"OpenIdAuth": ["scope1", "scope2"]}], + "isDefaultRoute": True, + "x-amazon-apigateway-integration": { + "httpMethod": "POST", + "type": "aws_proxy", + "uri": { + "Fn::If": [ + "condition", + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiFunctionAlias}/invocations" + }, + {"Ref": "AWS::NoValue"}, + ] + }, + "payloadFormatVersion": "1.0", + }, + "responses": {}, + }, + {"Ref": "AWS::NoValue"}, + ] + } + }, + "/bar": {}, + "/badpath": "string value", + }, + } + + self.editor = OpenApiEditor(self.original_openapi) + + def test_no_logical_id_if_alias(self): + + self.assertFalse( + self.editor.get_integration_function_logical_id(OpenApiEditor._DEFAULT_PATH, OpenApiEditor._X_ANY_METHOD), + ) From 08088b77a99a6c7f9692d436c96ecaac1e438cfe Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Mon, 1 Feb 2021 14:08:34 -0800 Subject: [PATCH 25/31] make black --- tests/openapi/test_openapi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/openapi/test_openapi.py b/tests/openapi/test_openapi.py index b398844977..f29cb4d47e 100644 --- a/tests/openapi/test_openapi.py +++ b/tests/openapi/test_openapi.py @@ -438,6 +438,7 @@ def test_must_not_add_description_if_already_defined(self): editor.add_description("New Description") self.assertEqual(editor.openapi["info"]["description"], "Existing Description") + class TestOpenApiEditor_get_integration_function_of_alias(TestCase): def setUp(self): From ecb089cabfc3486b3b04544cb713a3220c2ad17a Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Mon, 1 Feb 2021 14:29:07 -0800 Subject: [PATCH 26/31] Update comment --- samtranslator/open_api/open_api.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index 43e5d96341..d65085d0ea 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -105,9 +105,10 @@ def get_integration_function_logical_id(self, path_name, method_name): matches = re.findall(regex, arn) # For AWS::Serverless::Functions with AutoPublishAlias, the integration URI will # contain ${LogicalId} (where LogicalId is the logical ID of a AWS::Lambda::Alias), - # not ${LogicalId.Arn} (where LogicalId is the logical ID of a AWS::Lambda::Function). - # Without the .Arn suffix, parsing the URI is even more ambiguous, so instead of - # introducing more hacks, we return gracefully to prevent IndexErrors. + # not ${LogicalId.Arn} (where LogicalId is the logical ID of a AWS::Lambda::Function) + # (see intrinsics.make_shorthand and lambda_.LambdaAlias). Without the .Arn suffix, + # parsing the URI is even more ambiguous, so instead of introducing more hacks, we + # return gracefully to prevent IndexErrors. if not matches: return False match = matches[0].split(".Arn")[0] From 70b61e3c3b64b98616f47675bf0a286415bfd5ed Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Tue, 2 Feb 2021 21:02:48 -0800 Subject: [PATCH 27/31] Add integ test for default alias --- .../function_alias_with_http_api_event.json | 9 +++++++++ .../function_alias_with_http_api_event.yaml | 16 ++++++++++++++++ integration/single/test_basic_function.py | 10 +++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 integration/resources/expected/single/function_alias_with_http_api_event.json create mode 100644 integration/resources/templates/single/function_alias_with_http_api_event.yaml diff --git a/integration/resources/expected/single/function_alias_with_http_api_event.json b/integration/resources/expected/single/function_alias_with_http_api_event.json new file mode 100644 index 0000000000..a561d5bf30 --- /dev/null +++ b/integration/resources/expected/single/function_alias_with_http_api_event.json @@ -0,0 +1,9 @@ +[ + { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, + { "LogicalResourceId":"MyLambdaFunctionAliaslive", "ResourceType":"AWS::Lambda::Alias" }, + { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, + { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, + { "LogicalResourceId":"MyLambdaFunctionFooEventPermission", "ResourceType":"AWS::Lambda::Permission" }, + { "LogicalResourceId":"HttpApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, + { "LogicalResourceId":"HttpApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } +] diff --git a/integration/resources/templates/single/function_alias_with_http_api_event.yaml b/integration/resources/templates/single/function_alias_with_http_api_event.yaml new file mode 100644 index 0000000000..2cce1f1b69 --- /dev/null +++ b/integration/resources/templates/single/function_alias_with_http_api_event.yaml @@ -0,0 +1,16 @@ +Resources: + HttpApi: + Type: AWS::Serverless::HttpApi + MyLambdaFunction: + Type: AWS::Serverless::Function + Properties: + AutoPublishAlias: live + Handler: index.handler + Runtime: python3.8 + CodeUri: ${codeuri} + Events: + FooEvent: + Type: HttpApi + Properties: + ApiId: + Ref: HttpApi diff --git a/integration/single/test_basic_function.py b/integration/single/test_basic_function.py index 7367683b3f..a04aa97065 100644 --- a/integration/single/test_basic_function.py +++ b/integration/single/test_basic_function.py @@ -12,7 +12,6 @@ class TestBasicFunction(BaseTest): "basic_function", "basic_function_no_envvar", "basic_function_openapi", - "function_alias_with_http_api_events", ] ) def test_basic_function(self, file_name): @@ -27,6 +26,15 @@ def test_basic_function(self, file_name): self.assertEqual(self.get_resource_status_by_logical_id("MyLambdaFunction"), "UPDATE_COMPLETE") + @parameterized.expand( + [ + "function_alias_with_http_api_event", + "function_alias_with_http_api_events", + ] + ) + def test_function_alias(self, file_name): + self.create_and_verify_stack(file_name) + @parameterized.expand( [ ("basic_function_with_sns_dlq", "sns:Publish"), From 6ead5e48f11dded069e338e00714fd6cd7ed7527 Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Wed, 3 Feb 2021 23:22:26 -0800 Subject: [PATCH 28/31] Update tests --- ...api_event.json => function_with_http_api_events.json} | 2 -- ...api_event.yaml => function_with_http_api_events.yaml} | 8 +++++++- integration/single/test_basic_function.py | 4 ++-- samtranslator/open_api/open_api.py | 9 +++------ 4 files changed, 12 insertions(+), 11 deletions(-) rename integration/resources/expected/single/{function_alias_with_http_api_event.json => function_with_http_api_events.json} (71%) rename integration/resources/templates/single/{function_alias_with_http_api_event.yaml => function_with_http_api_events.yaml} (67%) diff --git a/integration/resources/expected/single/function_alias_with_http_api_event.json b/integration/resources/expected/single/function_with_http_api_events.json similarity index 71% rename from integration/resources/expected/single/function_alias_with_http_api_event.json rename to integration/resources/expected/single/function_with_http_api_events.json index a561d5bf30..71f09b5b5c 100644 --- a/integration/resources/expected/single/function_alias_with_http_api_event.json +++ b/integration/resources/expected/single/function_with_http_api_events.json @@ -1,8 +1,6 @@ [ { "LogicalResourceId":"MyLambdaFunction", "ResourceType":"AWS::Lambda::Function" }, - { "LogicalResourceId":"MyLambdaFunctionAliaslive", "ResourceType":"AWS::Lambda::Alias" }, { "LogicalResourceId":"MyLambdaFunctionRole", "ResourceType":"AWS::IAM::Role" }, - { "LogicalResourceId":"MyLambdaFunctionVersion", "ResourceType":"AWS::Lambda::Version" }, { "LogicalResourceId":"MyLambdaFunctionFooEventPermission", "ResourceType":"AWS::Lambda::Permission" }, { "LogicalResourceId":"HttpApi", "ResourceType":"AWS::ApiGatewayV2::Api" }, { "LogicalResourceId":"HttpApiApiGatewayDefaultStage", "ResourceType":"AWS::ApiGatewayV2::Stage" } diff --git a/integration/resources/templates/single/function_alias_with_http_api_event.yaml b/integration/resources/templates/single/function_with_http_api_events.yaml similarity index 67% rename from integration/resources/templates/single/function_alias_with_http_api_event.yaml rename to integration/resources/templates/single/function_with_http_api_events.yaml index 2cce1f1b69..8cfad07759 100644 --- a/integration/resources/templates/single/function_alias_with_http_api_event.yaml +++ b/integration/resources/templates/single/function_with_http_api_events.yaml @@ -4,7 +4,6 @@ Resources: MyLambdaFunction: Type: AWS::Serverless::Function Properties: - AutoPublishAlias: live Handler: index.handler Runtime: python3.8 CodeUri: ${codeuri} @@ -14,3 +13,10 @@ Resources: Properties: ApiId: Ref: HttpApi + BarEvent: + Type: HttpApi + Properties: + ApiId: + Ref: HttpApi + Path: /bar + Method: ANY diff --git a/integration/single/test_basic_function.py b/integration/single/test_basic_function.py index a04aa97065..3913070b51 100644 --- a/integration/single/test_basic_function.py +++ b/integration/single/test_basic_function.py @@ -28,11 +28,11 @@ def test_basic_function(self, file_name): @parameterized.expand( [ - "function_alias_with_http_api_event", "function_alias_with_http_api_events", + "function_with_http_api_events", ] ) - def test_function_alias(self, file_name): + def test_function_with_http_api_events(self, file_name): self.create_and_verify_stack(file_name) @parameterized.expand( diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index d65085d0ea..b7614daa91 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -103,12 +103,9 @@ def get_integration_function_logical_id(self, path_name, method_name): # Extract lambda integration (${LambdaName.Arn}) and split ".Arn" off from it regex = "([A-Za-z0-9]+\.Arn)" matches = re.findall(regex, arn) - # For AWS::Serverless::Functions with AutoPublishAlias, the integration URI will - # contain ${LogicalId} (where LogicalId is the logical ID of a AWS::Lambda::Alias), - # not ${LogicalId.Arn} (where LogicalId is the logical ID of a AWS::Lambda::Function) - # (see intrinsics.make_shorthand and lambda_.LambdaAlias). Without the .Arn suffix, - # parsing the URI is even more ambiguous, so instead of introducing more hacks, we - # return gracefully to prevent IndexErrors. + # Prevent IndexError when integration URI doesn't contain .Arn (e.g. a Function with + # AutoPublishAlias translates to AWS::Lambda::Alias, which make_shorthand represents + # as LogicalId instead of LogicalId.Arn). if not matches: return False match = matches[0].split(".Arn")[0] From 7d43bb1d1f39aa76a22b72328656c4c4a9b3eb67 Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Thu, 4 Feb 2021 17:36:14 -0800 Subject: [PATCH 29/31] Add TODO comment --- samtranslator/open_api/open_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/samtranslator/open_api/open_api.py b/samtranslator/open_api/open_api.py index b7614daa91..fc0f77a26e 100644 --- a/samtranslator/open_api/open_api.py +++ b/samtranslator/open_api/open_api.py @@ -106,6 +106,7 @@ def get_integration_function_logical_id(self, path_name, method_name): # Prevent IndexError when integration URI doesn't contain .Arn (e.g. a Function with # AutoPublishAlias translates to AWS::Lambda::Alias, which make_shorthand represents # as LogicalId instead of LogicalId.Arn). + # TODO: Consistent handling of Functions with and without AutoPublishAlias (see #1901) if not matches: return False match = matches[0].split(".Arn")[0] From 364eaa62c28e964c95ee21843ea74a0ef2861ce4 Mon Sep 17 00:00:00 2001 From: Chris Rehn Date: Mon, 8 Feb 2021 20:15:36 -0800 Subject: [PATCH 30/31] Add E2E test --- integration/helpers/base_test.py | 5 +++++ integration/resources/code/code.zip | Bin 224 -> 227 bytes .../function_alias_with_http_api_events.json | 4 ++-- .../single/function_with_http_api_events.json | 4 ++-- .../function_alias_with_http_api_events.yaml | 10 +++++----- .../single/function_with_http_api_events.yaml | 10 +++++----- integration/single/test_basic_function.py | 7 ++++++- 7 files changed, 25 insertions(+), 15 deletions(-) diff --git a/integration/helpers/base_test.py b/integration/helpers/base_test.py index 82af15d1da..77f5b1ec1d 100644 --- a/integration/helpers/base_test.py +++ b/integration/helpers/base_test.py @@ -216,6 +216,11 @@ def get_api_v2_stack_stages(self): return self.client_provider.api_v2_client.get_stages(ApiId=resources[0]["PhysicalResourceId"])["Items"] + def get_api_v2_endpoint(self, logical_id): + api_id = self.get_physical_id_by_logical_id(logical_id) + api = self.client_provider.api_v2_client.get_api(ApiId=api_id) + return api["ApiEndpoint"] + def get_stack_nested_stack_resources(self): resources = self.get_stack_resources("AWS::CloudFormation::Stack") diff --git a/integration/resources/code/code.zip b/integration/resources/code/code.zip index 3f21de8b6cf8b2ebb0ff6893c9eca604429098b1..cf321b5961e425512dc91fb689631cc7ae4e17c6 100644 GIT binary patch delta 144 zcmaFB_?Xctz?+$civa`z=XwNHiiU^V0@)zU!63tsnU|7Up_f%08p6rIyoyCB0fb8{ zxEUB(o- Date: Mon, 8 Feb 2021 20:26:15 -0800 Subject: [PATCH 31/31] Function output in constant --- integration/helpers/base_test.py | 1 + integration/single/test_basic_function.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/integration/helpers/base_test.py b/integration/helpers/base_test.py index 77f5b1ec1d..7b054c0e01 100644 --- a/integration/helpers/base_test.py +++ b/integration/helpers/base_test.py @@ -28,6 +28,7 @@ class BaseTest(TestCase): @classmethod def setUpClass(cls): + cls.FUNCTION_OUTPUT = "hello" cls.tests_integ_dir = Path(__file__).resolve().parents[1] cls.resources_dir = Path(cls.tests_integ_dir, "resources") cls.template_dir = Path(cls.resources_dir, "templates", "single") diff --git a/integration/single/test_basic_function.py b/integration/single/test_basic_function.py index debfab03f9..992a44245d 100644 --- a/integration/single/test_basic_function.py +++ b/integration/single/test_basic_function.py @@ -38,7 +38,7 @@ def test_function_with_http_api_events(self, file_name): endpoint = self.get_api_v2_endpoint("MyHttpApi") - self.assertEqual(requests.get(endpoint).text, "hello") + self.assertEqual(requests.get(endpoint).text, self.FUNCTION_OUTPUT) @parameterized.expand( [