diff --git a/.cfnlintrc.yaml b/.cfnlintrc.yaml index fa5e3cd3a2..a5a54820e7 100644 --- a/.cfnlintrc.yaml +++ b/.cfnlintrc.yaml @@ -130,6 +130,7 @@ ignore_templates: - tests/translator/output/**/managed_policies_minimal.json # Intentionally has non-existent managed policy name - tests/translator/output/**/function_with_mq.json # Property "EventSourceArn" can Fn::GetAtt to a resource of types [AWS::DynamoDB::GlobalTable, AWS::DynamoDB::Table, AWS::Kinesis::Stream, AWS::Kinesis::StreamConsumer, AWS::SQS::Queue] - tests/translator/output/**/function_with_mq_using_autogen_role.json # Property "EventSourceArn" can Fn::GetAtt to a resource of types [AWS::DynamoDB::GlobalTable, AWS::DynamoDB::Table, AWS::Kinesis::Stream, AWS::Kinesis::StreamConsumer, AWS::SQS::Queue] + - tests/translator/output/**/function_with_recursive_loop.json # Invalid Property Resources/RecursiveLoopParameterFunction/Properties/RecursiveLoop - tests/translator/output/**/function_with_tracing.json # Obsolete DependsOn on resource - tests/translator/output/**/api_with_propagate_tags.json # TODO: Intentional error transform tests. Will be updated. - tests/translator/output/**/function_with_intrinsics_resource_attribute.json # CFN now supports intrinsics in DeletionPolicy diff --git a/samtranslator/__init__.py b/samtranslator/__init__.py index 14b8b94b93..eb97acb8cb 100644 --- a/samtranslator/__init__.py +++ b/samtranslator/__init__.py @@ -1 +1 @@ -__version__ = "1.90.0" +__version__ = "1.91.0" diff --git a/samtranslator/internal/schema_source/aws_serverless_function.py b/samtranslator/internal/schema_source/aws_serverless_function.py index 7a19498380..74c252dceb 100644 --- a/samtranslator/internal/schema_source/aws_serverless_function.py +++ b/samtranslator/internal/schema_source/aws_serverless_function.py @@ -512,6 +512,7 @@ class ScheduleV2Event(BaseModel): SnapStart = Optional[PassThroughProp] # TODO: check the type RuntimeManagementConfig = Optional[PassThroughProp] # TODO: check the type LoggingConfig = Optional[PassThroughProp] # TODO: add documentation +RecursiveLoop = Optional[PassThroughProp] class Properties(BaseModel): @@ -638,6 +639,7 @@ class Properties(BaseModel): VersionDescription: Optional[PassThroughProp] = prop("VersionDescription") VpcConfig: Optional[VpcConfig] = prop("VpcConfig") LoggingConfig: Optional[PassThroughProp] # TODO: add documentation + RecursiveLoop: Optional[PassThroughProp] # TODO: add documentation class Globals(BaseModel): @@ -696,6 +698,7 @@ class Globals(BaseModel): SnapStart: Optional[SnapStart] = prop("SnapStart") RuntimeManagementConfig: Optional[RuntimeManagementConfig] = prop("RuntimeManagementConfig") LoggingConfig: Optional[PassThroughProp] # TODO: add documentation + RecursiveLoop: Optional[PassThroughProp] # TODO: add documentation class Resource(ResourceAttributes): diff --git a/samtranslator/model/lambda_.py b/samtranslator/model/lambda_.py index 28dc703b2a..e768097359 100644 --- a/samtranslator/model/lambda_.py +++ b/samtranslator/model/lambda_.py @@ -35,6 +35,7 @@ class LambdaFunction(Resource): "EphemeralStorage": GeneratedProperty(), "RuntimeManagementConfig": GeneratedProperty(), "LoggingConfig": GeneratedProperty(), + "RecursiveLoop": GeneratedProperty(), } Code: Dict[str, Any] @@ -62,6 +63,7 @@ class LambdaFunction(Resource): EphemeralStorage: Optional[Dict[str, Any]] RuntimeManagementConfig: Optional[Dict[str, Any]] LoggingConfig: Optional[Dict[str, Any]] + RecursiveLoop: Optional[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 1592ee8baa..37a6deb061 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -179,6 +179,7 @@ class SamFunction(SamResourceMacro): "FunctionUrlConfig": PropertyType(False, IS_DICT), "RuntimeManagementConfig": PassThroughProperty(False), "LoggingConfig": PassThroughProperty(False), + "RecursiveLoop": PassThroughProperty(False), } FunctionName: Optional[Intrinsicable[str]] @@ -221,6 +222,7 @@ class SamFunction(SamResourceMacro): SnapStart: Optional[Dict[str, Any]] FunctionUrlConfig: Optional[Dict[str, Any]] LoggingConfig: Optional[Dict[str, Any]] + RecursiveLoop: Optional[str] event_resolver = ResourceTypeResolver( samtranslator.model.eventsources, @@ -605,6 +607,7 @@ def _construct_lambda_function(self, intrinsics_resolver: IntrinsicsResolver) -> lambda_function.RuntimeManagementConfig = self.RuntimeManagementConfig # type: ignore[attr-defined] lambda_function.LoggingConfig = self.LoggingConfig + lambda_function.RecursiveLoop = self.RecursiveLoop self._validate_package_type(lambda_function) return lambda_function diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index 65adb6ac8c..d6836ff529 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -54,6 +54,7 @@ class Globals: "FunctionUrlConfig", "RuntimeManagementConfig", "LoggingConfig", + "RecursiveLoop", ], # Everything except # DefinitionBody: because its hard to reason about merge of Swagger dictionaries @@ -98,7 +99,7 @@ class Globals: } # unreleased_properties *must be* part of supported_properties too unreleased_properties: Dict[str, List[str]] = { - SamResourceType.Function.value: ["RuntimeManagementConfig"], + SamResourceType.Function.value: ["RuntimeManagementConfig", "RecursiveLoop"], } def __init__(self, template: Dict[str, Any]) -> None: diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index cf6c6c5c8a..38220d2934 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -278758,6 +278758,9 @@ "markdownDescription": "The provisioned concurrency configuration of a function's alias\\. \n`ProvisionedConcurrencyConfig` can be specified only if the `AutoPublishAlias` is set\\. Otherwise, an error results\\.\n*Type*: [ProvisionedConcurrencyConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-alias.html#cfn-lambda-alias-provisionedconcurrencyconfig) \n*Required*: No \n*AWS CloudFormation compatibility*: This property is passed directly to the [`ProvisionedConcurrencyConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-alias.html#cfn-lambda-alias-provisionedconcurrencyconfig) property of an `AWS::Lambda::Alias` resource\\.", "title": "ProvisionedConcurrencyConfig" }, + "RecursiveLoop": { + "$ref": "#/definitions/PassThroughProp" + }, "ReservedConcurrentExecutions": { "allOf": [ { @@ -279137,6 +279140,9 @@ "markdownDescription": "The provisioned concurrency configuration of a function's alias\\. \n`ProvisionedConcurrencyConfig` can be specified only if the `AutoPublishAlias` is set\\. Otherwise, an error results\\.\n*Type*: [ProvisionedConcurrencyConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-alias.html#cfn-lambda-alias-provisionedconcurrencyconfig) \n*Required*: No \n*AWS CloudFormation compatibility*: This property is passed directly to the [`ProvisionedConcurrencyConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-alias.html#cfn-lambda-alias-provisionedconcurrencyconfig) property of an `AWS::Lambda::Alias` resource\\.", "title": "ProvisionedConcurrencyConfig" }, + "RecursiveLoop": { + "$ref": "#/definitions/PassThroughProp" + }, "ReservedConcurrentExecutions": { "allOf": [ { diff --git a/schema_source/sam.schema.json b/schema_source/sam.schema.json index 48368977e4..9900d9de1f 100644 --- a/schema_source/sam.schema.json +++ b/schema_source/sam.schema.json @@ -5457,6 +5457,9 @@ "markdownDescription": "The provisioned concurrency configuration of a function's alias\\. \n`ProvisionedConcurrencyConfig` can be specified only if the `AutoPublishAlias` is set\\. Otherwise, an error results\\.\n*Type*: [ProvisionedConcurrencyConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-alias.html#cfn-lambda-alias-provisionedconcurrencyconfig) \n*Required*: No \n*AWS CloudFormation compatibility*: This property is passed directly to the [`ProvisionedConcurrencyConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-alias.html#cfn-lambda-alias-provisionedconcurrencyconfig) property of an `AWS::Lambda::Alias` resource\\.", "title": "ProvisionedConcurrencyConfig" }, + "RecursiveLoop": { + "$ref": "#/definitions/PassThroughProp" + }, "ReservedConcurrentExecutions": { "allOf": [ { @@ -6027,6 +6030,9 @@ ], "title": "ProvisionedConcurrencyConfig" }, + "RecursiveLoop": { + "$ref": "#/definitions/PassThroughProp" + }, "ReservedConcurrentExecutions": { "allOf": [ { diff --git a/tests/translator/input/function_with_recursive_loop.yaml b/tests/translator/input/function_with_recursive_loop.yaml new file mode 100644 index 0000000000..40a6ecf41a --- /dev/null +++ b/tests/translator/input/function_with_recursive_loop.yaml @@ -0,0 +1,21 @@ +Parameters: + RecursiveLoopParam: + Type: String + Default: ALLOW + +Resources: + RecursiveLoopFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.9 + RecursiveLoop: TERMINATE + + RecursiveLoopParameterFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.9 + RecursiveLoop: !Ref RecursiveLoopParam diff --git a/tests/translator/input/globals_for_function.yaml b/tests/translator/input/globals_for_function.yaml index 3cd3dbcfc0..464a73204b 100644 --- a/tests/translator/input/globals_for_function.yaml +++ b/tests/translator/input/globals_for_function.yaml @@ -32,6 +32,7 @@ Globals: UpdateRuntimeOn: Auto LoggingConfig: LogGroup: myJsonStructuredLogs + RecursiveLoop: ALLOW @@ -65,3 +66,4 @@ Resources: ReservedConcurrentExecutions: 100 RuntimeManagementConfig: UpdateRuntimeOn: FunctionChange + RecursiveLoop: TERMINATE diff --git a/tests/translator/output/aws-cn/function_with_recursive_loop.json b/tests/translator/output/aws-cn/function_with_recursive_loop.json new file mode 100644 index 0000000000..824ad47b97 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_recursive_loop.json @@ -0,0 +1,120 @@ +{ + "Parameters": { + "RecursiveLoopParam": { + "Default": "ALLOW", + "Type": "String" + } + }, + "Resources": { + "RecursiveLoopFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "RecursiveLoop": "TERMINATE", + "Role": { + "Fn::GetAtt": [ + "RecursiveLoopFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.9", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RecursiveLoopFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "RecursiveLoopParameterFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "RecursiveLoop": { + "Ref": "RecursiveLoopParam" + }, + "Role": { + "Fn::GetAtt": [ + "RecursiveLoopParameterFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.9", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RecursiveLoopParameterFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-cn/globals_for_function.json b/tests/translator/output/aws-cn/globals_for_function.json index ab1407f8cc..39b356f1ba 100644 --- a/tests/translator/output/aws-cn/globals_for_function.json +++ b/tests/translator/output/aws-cn/globals_for_function.json @@ -32,6 +32,7 @@ "LogGroup": "myJsonStructuredLogs" }, "MemorySize": 512, + "RecursiveLoop": "TERMINATE", "ReservedConcurrentExecutions": 100, "Role": { "Fn::GetAtt": [ @@ -51,13 +52,13 @@ "Key": "lambda:createdBy", "Value": "SAM" }, - { - "Key": "newtag1", - "Value": "newvalue1" - }, { "Key": "tag1", "Value": "value1" + }, + { + "Key": "newtag1", + "Value": "newvalue1" } ], "Timeout": 100, @@ -120,13 +121,13 @@ "Key": "lambda:createdBy", "Value": "SAM" }, - { - "Key": "newtag1", - "Value": "newvalue1" - }, { "Key": "tag1", "Value": "value1" + }, + { + "Key": "newtag1", + "Value": "newvalue1" } ] }, @@ -169,6 +170,7 @@ "LogGroup": "myJsonStructuredLogs" }, "MemorySize": 1024, + "RecursiveLoop": "ALLOW", "ReservedConcurrentExecutions": 50, "Role": { "Fn::GetAtt": [ diff --git a/tests/translator/output/aws-us-gov/function_with_recursive_loop.json b/tests/translator/output/aws-us-gov/function_with_recursive_loop.json new file mode 100644 index 0000000000..bbfd3c5a24 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_recursive_loop.json @@ -0,0 +1,120 @@ +{ + "Parameters": { + "RecursiveLoopParam": { + "Default": "ALLOW", + "Type": "String" + } + }, + "Resources": { + "RecursiveLoopFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "RecursiveLoop": "TERMINATE", + "Role": { + "Fn::GetAtt": [ + "RecursiveLoopFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.9", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RecursiveLoopFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "RecursiveLoopParameterFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "RecursiveLoop": { + "Ref": "RecursiveLoopParam" + }, + "Role": { + "Fn::GetAtt": [ + "RecursiveLoopParameterFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.9", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RecursiveLoopParameterFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-us-gov/globals_for_function.json b/tests/translator/output/aws-us-gov/globals_for_function.json index 34e2d5cff8..7cb1d98a80 100644 --- a/tests/translator/output/aws-us-gov/globals_for_function.json +++ b/tests/translator/output/aws-us-gov/globals_for_function.json @@ -32,6 +32,7 @@ "LogGroup": "myJsonStructuredLogs" }, "MemorySize": 512, + "RecursiveLoop": "TERMINATE", "ReservedConcurrentExecutions": 100, "Role": { "Fn::GetAtt": [ @@ -51,13 +52,13 @@ "Key": "lambda:createdBy", "Value": "SAM" }, - { - "Key": "newtag1", - "Value": "newvalue1" - }, { "Key": "tag1", "Value": "value1" + }, + { + "Key": "newtag1", + "Value": "newvalue1" } ], "Timeout": 100, @@ -120,13 +121,13 @@ "Key": "lambda:createdBy", "Value": "SAM" }, - { - "Key": "newtag1", - "Value": "newvalue1" - }, { "Key": "tag1", "Value": "value1" + }, + { + "Key": "newtag1", + "Value": "newvalue1" } ] }, @@ -169,6 +170,7 @@ "LogGroup": "myJsonStructuredLogs" }, "MemorySize": 1024, + "RecursiveLoop": "ALLOW", "ReservedConcurrentExecutions": 50, "Role": { "Fn::GetAtt": [ diff --git a/tests/translator/output/function_with_recursive_loop.json b/tests/translator/output/function_with_recursive_loop.json new file mode 100644 index 0000000000..e771f4a9f7 --- /dev/null +++ b/tests/translator/output/function_with_recursive_loop.json @@ -0,0 +1,120 @@ +{ + "Parameters": { + "RecursiveLoopParam": { + "Default": "ALLOW", + "Type": "String" + } + }, + "Resources": { + "RecursiveLoopFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "RecursiveLoop": "TERMINATE", + "Role": { + "Fn::GetAtt": [ + "RecursiveLoopFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.9", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RecursiveLoopFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + }, + "RecursiveLoopParameterFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "RecursiveLoop": { + "Ref": "RecursiveLoopParam" + }, + "Role": { + "Fn::GetAtt": [ + "RecursiveLoopParameterFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.9", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RecursiveLoopParameterFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/globals_for_function.json b/tests/translator/output/globals_for_function.json index ae9543ab6a..375a948ca6 100644 --- a/tests/translator/output/globals_for_function.json +++ b/tests/translator/output/globals_for_function.json @@ -32,6 +32,7 @@ "LogGroup": "myJsonStructuredLogs" }, "MemorySize": 512, + "RecursiveLoop": "TERMINATE", "ReservedConcurrentExecutions": 100, "Role": { "Fn::GetAtt": [ @@ -51,13 +52,13 @@ "Key": "lambda:createdBy", "Value": "SAM" }, - { - "Key": "newtag1", - "Value": "newvalue1" - }, { "Key": "tag1", "Value": "value1" + }, + { + "Key": "newtag1", + "Value": "newvalue1" } ], "Timeout": 100, @@ -120,13 +121,13 @@ "Key": "lambda:createdBy", "Value": "SAM" }, - { - "Key": "newtag1", - "Value": "newvalue1" - }, { "Key": "tag1", "Value": "value1" + }, + { + "Key": "newtag1", + "Value": "newvalue1" } ] }, @@ -169,6 +170,7 @@ "LogGroup": "myJsonStructuredLogs" }, "MemorySize": 1024, + "RecursiveLoop": "ALLOW", "ReservedConcurrentExecutions": 50, "Role": { "Fn::GetAtt": [