From b15cba79b6f6840cd41e069c8f2b94c1ad337e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Morosi?= Date: Tue, 16 Jan 2024 15:03:31 +0100 Subject: [PATCH] Improve RestApi for stages and aliases The possibility to have different lambda functions depending on REST API methods doesn't integrate well with the current way of defining stages and lambda aliases. As each stage is defined once, the stage variables must be the same for all methods and all lambda functions. So if there is a stage variable pointing to a lambda alias, it must be the same for all lambda functions. Hence a change is made to Alias so that the resource name can be different from the alias name, and multiple lambda functions can share the same alias name. Then the possibility to define an integration uri at the resource level is added so that each resource can point to a different lambda function while still referencing the stage variable containing the alias name. Finally, the possibility to define the lambda ARN for the InvokeFunction permission at the resource level, and per stage, is added to make it possible to give the right permissions for each resource and stage. Simplify test_rest_api_nested_resources as there is now test_rest_api_multi_lambdas_stages --- src/e3/aws/troposphere/apigateway/__init__.py | 104 ++++- src/e3/aws/troposphere/awslambda/__init__.py | 17 +- .../troposphere/apigateway/apigateway_test.py | 112 ++++- ...pigatewayv1_test_multi_lambdas_stages.json | 420 ++++++++++++++++++ .../apigatewayv1_test_nested_resources.json | 97 +--- .../troposphere/awslambda/awslambda_test.py | 4 +- 6 files changed, 633 insertions(+), 121 deletions(-) create mode 100644 tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_multi_lambdas_stages.json diff --git a/src/e3/aws/troposphere/apigateway/__init__.py b/src/e3/aws/troposphere/apigateway/__init__.py index c5c230f..4627635 100644 --- a/src/e3/aws/troposphere/apigateway/__init__.py +++ b/src/e3/aws/troposphere/apigateway/__init__.py @@ -170,19 +170,32 @@ def __init__( path: str, method_list: list[Method], resource_list: list[Resource] | None = None, + integration_uri: str | Ref | Sub | None = None, lambda_arn: str | GetAtt | Ref | None = None, + lambda_arn_permission: str + | GetAtt + | Ref + | dict[str, str | GetAtt | Ref] + | None = None, ) -> None: """Initialize a REST API resource. :param path: the last path segment for this resource :param method_list: a list of methods accepted on this resource :param resource_list: a list of child resources + :param integration_uri: URI of a lambda function for this resource :param lambda_arn: arn of the lambda executed for this resource + :param lambda_arn_permission: lambda arn for which to add InvokeFunction + permission (can be different from the lambda arn executed + by the REST API). A mapping from stage names to lambda arns can + also be passed """ self.path = path self.method_list = method_list self.resource_list = resource_list + self.integration_uri = integration_uri self.lambda_arn = lambda_arn + self.lambda_arn_permission = lambda_arn_permission class Api(Construct): @@ -224,7 +237,6 @@ def __init__( :param hosted_zone_id: id of the hosted zone that contains domain_name. This parameter is required if domain_name is not None :param stages_config: configurations of the different stages - :param integration_uri: URI of a Lambda function """ self.name = name self.description = description @@ -895,35 +907,39 @@ def declare_stage( def _declare_method( self, method: Method, - resource: Resource, resource_id_prefix: str, resource_path: str, + resource_integration_uri: str | Ref | Sub | None = None, + resource_lambda_arn: str | GetAtt | Ref | None = None, + resource_lambda_arn_permission: str + | GetAtt + | Ref + | dict[str, str | GetAtt | Ref] + | None = None, ) -> list[AWSObject]: """Declare a method. :param method: the method definition - :param resource: resource associated with the method :param resource_id_prefix: resource_id without trailing Resource :param resource_path: absolute path to the resource + :param resource_integration_uri: integration URI for the resource + :param resource_lambda_arn: arn of lambda for the resource + :param resource_lambda_arn_permission: lambda arn permission for the resource :return: a list of AWSObjects to be added to the stack """ result = [] id_prefix = name_to_id(f"{resource_id_prefix}-{method.method}") - # Take the global lambda_arn or the one configured for the resource - lambda_arn = ( - self.lambda_arn if resource.lambda_arn is None else resource.lambda_arn - ) - - # Integration URI for the resource + # Take the global integration uri or the one configured for the resource integration_uri = ( self.integration_uri - if self.integration_uri is not None - else Sub( - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31" - "/functions/${lambdaArn}/invocations", - dict_values={"lambdaArn": lambda_arn}, - ) + if resource_integration_uri is None + else resource_integration_uri + ) + + # Take the global lambda arn or the one configured for the resource + lambda_arn = ( + self.lambda_arn if resource_lambda_arn is None else resource_lambda_arn ) integration = apigateway.Integration( @@ -934,7 +950,13 @@ def _declare_method( IntegrationHttpMethod="POST", PassthroughBehavior="NEVER", Type="AWS_PROXY", - Uri=integration_uri, + Uri=integration_uri + if integration_uri is not None + else Sub( + "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31" + "/functions/${lambdaArn}/invocations", + dict_values={"lambdaArn": lambda_arn}, + ), ) method_params = { @@ -952,6 +974,16 @@ def _declare_method( result.append(apigateway.Method(f"{id_prefix}Method", **method_params)) for config in self.stages_config: + if resource_lambda_arn_permission is not None: + # Use the lambda_arn_permission configured for resource + if isinstance(resource_lambda_arn_permission, dict): + assert ( + config.name in resource_lambda_arn_permission + ), f"missing lambda arn permission for stage {config.name}" + lambda_arn = resource_lambda_arn_permission[config.name] + else: + lambda_arn = resource_lambda_arn_permission + result.append( awslambda.Permission( name_to_id(f"{id_prefix}-{config.name}LambdaPermission"), @@ -1019,6 +1051,13 @@ def _declare_resources( resource_list: list[Resource], parent_id_prefix: str | None = None, parent_path: str | None = None, + parent_integration_uri: str | Ref | Sub | None = None, + parent_lambda_arn: str | GetAtt | Ref | None = None, + parent_lambda_arn_permission: str + | GetAtt + | Ref + | dict[str, str | GetAtt | Ref] + | None = None, ) -> list[AWSObject]: """Create API resources and methods recursively. @@ -1026,6 +1065,11 @@ def _declare_resources( :param resource_list: list of resources :param parent_id_prefix: id of the parent resource without trailing Resource + :param parent_path: absolute path to the parent resource + :param parent_integration_uri: integration URI of the parent resource + :param parent_lambda_arn: lambda arn of the parent resource + :param parent_lambda_arn_permission: lambda arn permission of the + parent resource :return: a list of AWSObjects to be added to the stack """ result: list[AWSObject] = [] @@ -1059,13 +1103,36 @@ def _declare_resources( result.append(resource) + # Get the integration URI of this resource. + # It must be forwarded to children so that they recursively use the + # same URI + resource_integration_uri = ( + r.integration_uri + if r.integration_uri is not None + else parent_integration_uri + ) + + # Same for the lambda arn + resource_lambda_arn = ( + r.lambda_arn if r.lambda_arn is not None else parent_lambda_arn + ) + + # Same fo the lambda arn permission + resource_lambda_arn_permission = ( + r.lambda_arn_permission + if r.lambda_arn_permission is not None + else parent_lambda_arn_permission + ) + # Declare the methods of this resource for method in r.method_list: result += self._declare_method( method=method, - resource=r, resource_id_prefix=resource_id_prefix, resource_path=resource_path, + resource_integration_uri=resource_integration_uri, + resource_lambda_arn=resource_lambda_arn, + resource_lambda_arn_permission=resource_lambda_arn_permission, ) # Declare the children of this resource @@ -1074,6 +1141,9 @@ def _declare_resources( resource_list=r.resource_list, parent_id_prefix=resource_id_prefix, parent_path=resource_path, + parent_integration_uri=resource_integration_uri, + parent_lambda_arn=resource_lambda_arn, + parent_lambda_arn_permission=resource_lambda_arn_permission, ) return result diff --git a/src/e3/aws/troposphere/awslambda/__init__.py b/src/e3/aws/troposphere/awslambda/__init__.py index 2a66988..3ca1d7d 100644 --- a/src/e3/aws/troposphere/awslambda/__init__.py +++ b/src/e3/aws/troposphere/awslambda/__init__.py @@ -509,22 +509,29 @@ def __init__( description: str, lambda_arn: str | GetAtt | Ref, lambda_version: str, + alias_name: str | None = None, provisioned_concurrency_config: awslambda.ProvisionedConcurrencyConfiguration | None = None, routing_config: awslambda.AliasRoutingConfiguration | None = None, ): """Initialize an AWS lambda alias. - :param name: function name - :param description: a description of the function + :param name: name of the resource + :param description: a description of the alias :param lambda_arn: the name of the Lambda function :param lambda_version: the function version that the alias invokes + :param alias_name: name of the alias. By default the parameter + name will be used as both the name of the resource and the name + of the alias, so this allows for a different alias name. For + example if you have multiple Lambda functions using the same + alias names :param provisioned_concurrency_config: specifies a provisioned concurrency configuration for a function's alias :param routing_config: the routing configuration of the alias """ self.name = name self.description = description + self.alias_name = alias_name self.lambda_arn = lambda_arn self.lambda_version = lambda_version self.provisioned_concurrency_config = provisioned_concurrency_config @@ -537,7 +544,7 @@ def ref(self) -> Ref: def resources(self, stack: Stack) -> list[AWSObject]: """Return list of AWSObject associated with the construct.""" params = { - "Name": self.name, + "Name": self.alias_name if self.alias_name is not None else self.name, "Description": self.description, "FunctionName": self.lambda_arn, "FunctionVersion": self.lambda_version, @@ -769,9 +776,11 @@ def create_alias( :param default_name: default alias name if none is specified """ name = config.name if config.name is not None else default_name + id = name_to_id(f"{self.lambda_name}-{name}-alias") return Alias( - name=name_to_id(f"{self.lambda_name}-{name}-alias"), + name=id, description=f"{name} alias for {self.lambda_name} lambda", + alias_name=config.name if config.name is not None else id, lambda_arn=self.lambda_arn, lambda_version=config.version, provisioned_concurrency_config=config.provisioned_concurrency_config, diff --git a/tests/tests_e3_aws/troposphere/apigateway/apigateway_test.py b/tests/tests_e3_aws/troposphere/apigateway/apigateway_test.py index f3c5825..499dd23 100644 --- a/tests/tests_e3_aws/troposphere/apigateway/apigateway_test.py +++ b/tests/tests_e3_aws/troposphere/apigateway/apigateway_test.py @@ -714,29 +714,15 @@ def test_rest_api_nested_resources(stack: Stack, lambda_fun: PyFunction) -> None stack.s3_bucket = "cfn_bucket" stack.s3_key = "templates/" - # Lambda for the products resource - products_lambda = PyFunction( - name="productslambda", - description="this is a test", - role="somearn", - code_dir="my_code_dir", - handler="app.main", - runtime="python3.8", - logs_retention_in_days=None, - ) - rest_api = RestApi( name="testapi", description="this is a test", lambda_arn=lambda_fun.ref, resource_list=[ - Resource(path="accounts", method_list=[Method("ANY")]), Resource( - path="products", - # Specific lambda for this resource - lambda_arn=products_lambda.ref, + path="foo", method_list=[Method("ANY")], - resource_list=[Resource(path="abcd", method_list=[Method("GET")])], + resource_list=[Resource(path="bar", method_list=[Method("GET")])], ), ], ) @@ -751,3 +737,97 @@ def test_rest_api_nested_resources(stack: Stack, lambda_fun: PyFunction) -> None print(stack.export()["Resources"]) assert stack.export()["Resources"] == expected + + +def test_rest_api_multi_lambdas_stages(stack: Stack) -> None: + """Test REST API with multiple lambdas and stages.""" + stack.s3_bucket = "cfn_bucket" + stack.s3_key = "templates/" + + # Create two lambdas for two different methods + accounts_lambda, products_lambda = [ + PyFunction( + name=f"{name}lambda", + description="this is a test", + role="somearn", + code_dir="my_code_dir", + handler="app.main", + runtime="python3.8", + logs_retention_in_days=None, + ) + for name in ("accounts", "products") + ] + + # Create lambda versions + accounts_lambda_versions, products_lambda_versions = [ + AutoVersion(2, lambda_function=lambda_fun) + for lambda_fun in (accounts_lambda, products_lambda) + ] + + # Create lambda aliases. + # Share the same alias names as it will make it easier to setup the stage + # variable for using the right alias depending on the stage + accounts_lambda_aliases, products_lambda_aliases = [ + BlueGreenAliases( + blue_config=BlueGreenAliasConfiguration( + name="Blue", version=lambda_versions.previous.version + ), + green_config=BlueGreenAliasConfiguration( + name="Green", version=lambda_versions.latest.version + ), + lambda_function=lambda_fun, + ) + for lambda_versions, lambda_fun in ( + (accounts_lambda_versions, accounts_lambda), + (products_lambda_versions, products_lambda), + ) + ] + + # Create the REST API + rest_api = RestApi( + name="testapi", + description="this is a test", + # Not important as it's overriden in resources + lambda_arn=accounts_lambda.ref, + # Declare prod and beta stages redirecting to correct aliases + stages_config=[ + StageConfiguration("default", variables={"lambdaAlias": "Blue"}), + StageConfiguration("beta", variables={"lambdaAlias": "Green"}), + ], + # Declare two resources pointing to two different lambdas + resource_list=[ + Resource( + path=path, + # Action to invoke the lambda with correct alias + integration_uri="arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/" + "functions/arn:aws:lambda:eu-west-1:123456789012:function:" + f"{lambda_fun.name}:${{stageVariables.lambdaAlias}}/invocations", + # Lambda ARNs for InvokeFunction permissions depending on the stage + lambda_arn_permission={ + "default": lambda_aliases.blue.ref, + "beta": lambda_aliases.green.ref, + }, + method_list=[Method("ANY")], + ) + for path, lambda_fun, lambda_aliases in ( + ("accounts", accounts_lambda, accounts_lambda_aliases), + ("products", products_lambda, products_lambda_aliases), + ) + ], + ) + + stack.add(accounts_lambda) + stack.add(products_lambda) + stack.add(accounts_lambda_versions) + stack.add(products_lambda_versions) + stack.add(accounts_lambda_aliases) + stack.add(products_lambda_aliases) + stack.add(rest_api) + + with open( + os.path.join(TEST_DIR, "apigatewayv1_test_multi_lambdas_stages.json"), + ) as fd: + expected = json.load(fd) + + print(stack.export()["Resources"]) + assert stack.export()["Resources"] == expected diff --git a/tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_multi_lambdas_stages.json b/tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_multi_lambdas_stages.json new file mode 100644 index 0000000..b873952 --- /dev/null +++ b/tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_multi_lambdas_stages.json @@ -0,0 +1,420 @@ +{ + "Accountslambda": { + "Properties": { + "Code": { + "S3Bucket": "cfn_bucket", + "S3Key": "templates/accountslambda_lambda.zip" + }, + "Timeout": 3, + "Description": "this is a test", + "Role": "somearn", + "FunctionName": "accountslambda", + "Runtime": "python3.8", + "Handler": "app.main" + }, + "Type": "AWS::Lambda::Function" + }, + "Productslambda": { + "Properties": { + "Code": { + "S3Bucket": "cfn_bucket", + "S3Key": "templates/productslambda_lambda.zip" + }, + "Timeout": 3, + "Description": "this is a test", + "Role": "somearn", + "FunctionName": "productslambda", + "Runtime": "python3.8", + "Handler": "app.main" + }, + "Type": "AWS::Lambda::Function" + }, + "AccountslambdaVersion1": { + "Properties": { + "Description": "version 1 of accountslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Accountslambda", + "Arn" + ] + } + }, + "Type": "AWS::Lambda::Version" + }, + "AccountslambdaVersion2": { + "Properties": { + "Description": "version 2 of accountslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Accountslambda", + "Arn" + ] + } + }, + "Type": "AWS::Lambda::Version" + }, + "ProductslambdaVersion1": { + "Properties": { + "Description": "version 1 of productslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Productslambda", + "Arn" + ] + } + }, + "Type": "AWS::Lambda::Version" + }, + "ProductslambdaVersion2": { + "Properties": { + "Description": "version 2 of productslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Productslambda", + "Arn" + ] + } + }, + "Type": "AWS::Lambda::Version" + }, + "AccountslambdaBlueAlias": { + "Properties": { + "Description": "Blue alias for accountslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Accountslambda", + "Arn" + ] + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "AccountslambdaVersion1", + "Version" + ] + }, + "Name": "Blue" + }, + "Type": "AWS::Lambda::Alias" + }, + "AccountslambdaGreenAlias": { + "Properties": { + "Description": "Green alias for accountslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Accountslambda", + "Arn" + ] + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "AccountslambdaVersion2", + "Version" + ] + }, + "Name": "Green" + }, + "Type": "AWS::Lambda::Alias" + }, + "ProductslambdaBlueAlias": { + "Properties": { + "Description": "Blue alias for productslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Productslambda", + "Arn" + ] + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "ProductslambdaVersion1", + "Version" + ] + }, + "Name": "Blue" + }, + "Type": "AWS::Lambda::Alias" + }, + "ProductslambdaGreenAlias": { + "Properties": { + "Description": "Green alias for productslambda lambda", + "FunctionName": { + "Fn::GetAtt": [ + "Productslambda", + "Arn" + ] + }, + "FunctionVersion": { + "Fn::GetAtt": [ + "ProductslambdaVersion2", + "Version" + ] + }, + "Name": "Green" + }, + "Type": "AWS::Lambda::Alias" + }, + "TestapiLogGroup": { + "Properties": { + "LogGroupName": "testapi" + }, + "Type": "AWS::Logs::LogGroup" + }, + "Testapi": { + "Properties": { + "Description": "this is a test", + "Name": "testapi", + "DisableExecuteApiEndpoint": false + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "TestapiAccountsResource": { + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "Testapi", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "Testapi" + }, + "PathPart": "accounts" + }, + "Type": "AWS::ApiGateway::Resource" + }, + "TestapiProductsResource": { + "Properties": { + "ParentId": { + "Fn::GetAtt": [ + "Testapi", + "RootResourceId" + ] + }, + "RestApiId": { + "Ref": "Testapi" + }, + "PathPart": "products" + }, + "Type": "AWS::ApiGateway::Resource" + }, + "TestapiDefaultDeployment": { + "Properties": { + "Description": "Deployment resource of default stage", + "RestApiId": { + "Ref": "Testapi" + } + }, + "Type": "AWS::ApiGateway::Deployment", + "DependsOn": [ + "TestapiAccountsANYMethod", + "TestapiProductsANYMethod" + ] + }, + "TestapiBetaDeployment": { + "Properties": { + "Description": "Deployment resource of beta stage", + "RestApiId": { + "Ref": "Testapi" + } + }, + "Type": "AWS::ApiGateway::Deployment", + "DependsOn": [ + "TestapiAccountsANYMethod", + "TestapiProductsANYMethod" + ] + }, + "TestapiDefaultStage": { + "Properties": { + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "TestapiLogGroup", + "Arn" + ] + }, + "Format": "{\"source_ip\": \"$context.identity.sourceIp\", \"request_time\": \"$context.requestTime\", \"method\": \"$context.httpMethod\", \"route\": \"$context.routeKey\", \"protocol\": \"$context.protocol\", \"status\": \"$context.status\", \"response_length\": \"$context.responseLength\", \"request_id\": \"$context.requestId\", \"integration_error_msg\": \"$context.integrationErrorMessage\"}" + }, + "RestApiId": { + "Ref": "Testapi" + }, + "DeploymentId": { + "Ref": "TestapiDefaultDeployment" + }, + "Description": "stage default", + "MethodSettings": [ + { + "ResourcePath": "/*", + "HttpMethod": "*", + "MetricsEnabled": true, + "ThrottlingBurstLimit": 10, + "ThrottlingRateLimit": 10 + } + ], + "StageName": "default", + "Variables": { + "lambdaAlias": "Blue" + } + }, + "Type": "AWS::ApiGateway::Stage" + }, + "TestapiBetaStage": { + "Properties": { + "AccessLogSetting": { + "DestinationArn": { + "Fn::GetAtt": [ + "TestapiLogGroup", + "Arn" + ] + }, + "Format": "{\"source_ip\": \"$context.identity.sourceIp\", \"request_time\": \"$context.requestTime\", \"method\": \"$context.httpMethod\", \"route\": \"$context.routeKey\", \"protocol\": \"$context.protocol\", \"status\": \"$context.status\", \"response_length\": \"$context.responseLength\", \"request_id\": \"$context.requestId\", \"integration_error_msg\": \"$context.integrationErrorMessage\"}" + }, + "RestApiId": { + "Ref": "Testapi" + }, + "DeploymentId": { + "Ref": "TestapiBetaDeployment" + }, + "Description": "stage beta", + "MethodSettings": [ + { + "ResourcePath": "/*", + "HttpMethod": "*", + "MetricsEnabled": true, + "ThrottlingBurstLimit": 10, + "ThrottlingRateLimit": 10 + } + ], + "StageName": "beta", + "Variables": { + "lambdaAlias": "Green" + } + }, + "Type": "AWS::ApiGateway::Stage" + }, + "TestapiAccountsANYMethod": { + "Properties": { + "RestApiId": { + "Ref": "Testapi" + }, + "AuthorizationType": "NONE", + "HttpMethod": "ANY", + "Integration": { + "CacheKeyParameters": [], + "CacheNamespace": "none", + "IntegrationHttpMethod": "POST", + "PassthroughBehavior": "NEVER", + "Type": "AWS_PROXY", + "Uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:123456789012:function:accountslambda:${stageVariables.lambdaAlias}/invocations" + }, + "ResourceId": { + "Ref": "TestapiAccountsResource" + } + }, + "Type": "AWS::ApiGateway::Method" + }, + "TestapiProductsANYMethod": { + "Properties": { + "RestApiId": { + "Ref": "Testapi" + }, + "AuthorizationType": "NONE", + "HttpMethod": "ANY", + "Integration": { + "CacheKeyParameters": [], + "CacheNamespace": "none", + "IntegrationHttpMethod": "POST", + "PassthroughBehavior": "NEVER", + "Type": "AWS_PROXY", + "Uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:eu-west-1:123456789012:function:productslambda:${stageVariables.lambdaAlias}/invocations" + }, + "ResourceId": { + "Ref": "TestapiProductsResource" + } + }, + "Type": "AWS::ApiGateway::Method" + }, + "TestapiAccountsANYDefaultLambdaPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "AccountslambdaBlueAlias" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/default/${method}/accounts", + { + "api": { + "Ref": "Testapi" + }, + "method": "*" + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "TestapiAccountsANYBetaLambdaPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "AccountslambdaGreenAlias" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/beta/${method}/accounts", + { + "api": { + "Ref": "Testapi" + }, + "method": "*" + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "TestapiProductsANYDefaultLambdaPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "ProductslambdaBlueAlias" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/default/${method}/products", + { + "api": { + "Ref": "Testapi" + }, + "method": "*" + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + }, + "TestapiProductsANYBetaLambdaPermission": { + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Ref": "ProductslambdaGreenAlias" + }, + "Principal": "apigateway.amazonaws.com", + "SourceArn": { + "Fn::Sub": [ + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/beta/${method}/products", + { + "api": { + "Ref": "Testapi" + }, + "method": "*" + } + ] + } + }, + "Type": "AWS::Lambda::Permission" + } +} \ No newline at end of file diff --git a/tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_nested_resources.json b/tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_nested_resources.json index 14e3ccb..958a9ae 100644 --- a/tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_nested_resources.json +++ b/tests/tests_e3_aws/troposphere/apigateway/apigatewayv1_test_nested_resources.json @@ -28,7 +28,7 @@ }, "Type": "AWS::ApiGateway::RestApi" }, - "TestapiAccountsResource": { + "TestapiFooResource": { "Properties": { "ParentId": { "Fn::GetAtt": [ @@ -39,37 +39,22 @@ "RestApiId": { "Ref": "Testapi" }, - "PathPart": "accounts" + "PathPart": "foo" }, "Type": "AWS::ApiGateway::Resource" }, - "TestapiProductsResource": { + "TestapiFooBarResource": { "Properties": { "ParentId": { "Fn::GetAtt": [ - "Testapi", - "RootResourceId" - ] - }, - "RestApiId": { - "Ref": "Testapi" - }, - "PathPart": "products" - }, - "Type": "AWS::ApiGateway::Resource" - }, - "TestapiProductsAbcdResource": { - "Properties": { - "ParentId": { - "Fn::GetAtt": [ - "TestapiProductsResource", + "TestapiFooResource", "ResourceId" ] }, "RestApiId": { "Ref": "Testapi" }, - "PathPart": "abcd" + "PathPart": "bar" }, "Type": "AWS::ApiGateway::Resource" }, @@ -82,9 +67,8 @@ }, "Type": "AWS::ApiGateway::Deployment", "DependsOn": [ - "TestapiAccountsANYMethod", - "TestapiProductsANYMethod", - "TestapiProductsAbcdGETMethod" + "TestapiFooANYMethod", + "TestapiFooBarGETMethod" ] }, "TestapiDefaultStage": { @@ -118,7 +102,7 @@ }, "Type": "AWS::ApiGateway::Stage" }, - "TestapiAccountsANYMethod": { + "TestapiFooANYMethod": { "Properties": { "RestApiId": { "Ref": "Testapi" @@ -143,42 +127,12 @@ } }, "ResourceId": { - "Ref": "TestapiAccountsResource" - } - }, - "Type": "AWS::ApiGateway::Method" - }, - "TestapiProductsANYMethod": { - "Properties": { - "RestApiId": { - "Ref": "Testapi" - }, - "AuthorizationType": "NONE", - "HttpMethod": "ANY", - "Integration": { - "CacheKeyParameters": [], - "CacheNamespace": "none", - "IntegrationHttpMethod": "POST", - "PassthroughBehavior": "NEVER", - "Type": "AWS_PROXY", - "Uri": { - "Fn::Sub": [ - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations", - { - "lambdaArn": { - "Ref": "Productslambda" - } - } - ] - } - }, - "ResourceId": { - "Ref": "TestapiProductsResource" + "Ref": "TestapiFooResource" } }, "Type": "AWS::ApiGateway::Method" }, - "TestapiProductsAbcdGETMethod": { + "TestapiFooBarGETMethod": { "Properties": { "RestApiId": { "Ref": "Testapi" @@ -203,12 +157,12 @@ } }, "ResourceId": { - "Ref": "TestapiProductsAbcdResource" + "Ref": "TestapiFooBarResource" } }, "Type": "AWS::ApiGateway::Method" }, - "TestapiAccountsANYDefaultLambdaPermission": { + "TestapiFooANYDefaultLambdaPermission": { "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { @@ -217,28 +171,7 @@ "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/default/${method}/accounts", - { - "api": { - "Ref": "Testapi" - }, - "method": "*" - } - ] - } - }, - "Type": "AWS::Lambda::Permission" - }, - "TestapiProductsANYDefaultLambdaPermission": { - "Properties": { - "Action": "lambda:InvokeFunction", - "FunctionName": { - "Ref": "Productslambda" - }, - "Principal": "apigateway.amazonaws.com", - "SourceArn": { - "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/default/${method}/products", + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/default/${method}/foo", { "api": { "Ref": "Testapi" @@ -250,7 +183,7 @@ }, "Type": "AWS::Lambda::Permission" }, - "TestapiProductsAbcdGETDefaultLambdaPermission": { + "TestapiFooBarGETDefaultLambdaPermission": { "Properties": { "Action": "lambda:InvokeFunction", "FunctionName": { @@ -259,7 +192,7 @@ "Principal": "apigateway.amazonaws.com", "SourceArn": { "Fn::Sub": [ - "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/default/${method}/products/abcd", + "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${api}/default/${method}/foo/bar", { "api": { "Ref": "Testapi" diff --git a/tests/tests_e3_aws/troposphere/awslambda/awslambda_test.py b/tests/tests_e3_aws/troposphere/awslambda/awslambda_test.py index d03f3c1..527aa14 100644 --- a/tests/tests_e3_aws/troposphere/awslambda/awslambda_test.py +++ b/tests/tests_e3_aws/troposphere/awslambda/awslambda_test.py @@ -284,7 +284,7 @@ EXPECTED_BLUEGREENALIASES_TEMPLATE = { "MypylambdaProdAlias": { "Properties": { - "Name": "MypylambdaProdAlias", + "Name": "prod", "Description": "prod alias for mypylambda lambda", "FunctionName": {"Fn::GetAtt": ["Mypylambda", "Arn"]}, "FunctionVersion": {"Ref": "MypylambdaVersion1"}, @@ -304,7 +304,7 @@ }, "MypylambdaBetaAlias": { "Properties": { - "Name": "MypylambdaBetaAlias", + "Name": "beta", "Description": "beta alias for mypylambda lambda", "FunctionName": {"Fn::GetAtt": ["Mypylambda", "Arn"]}, "FunctionVersion": {"Ref": "MypylambdaVersion2"},