diff --git a/samtranslator/internal/schema_source/aws_serverless_function.py b/samtranslator/internal/schema_source/aws_serverless_function.py index 811f37d1b..328100023 100644 --- a/samtranslator/internal/schema_source/aws_serverless_function.py +++ b/samtranslator/internal/schema_source/aws_serverless_function.py @@ -44,6 +44,7 @@ httpapieventproperties = get_prop("sam-property-function-httpapi") iotruleeventproperties = get_prop("sam-property-function-iotrule") kinesiseventproperties = get_prop("sam-property-function-kinesis") +loggroup = get_prop("sam-property-function-loggroup") mqeventproperties = get_prop("sam-property-function-mq") mskeventproperties = get_prop("sam-property-function-msk") prop = get_prop(PROPERTIES_STEM) @@ -576,6 +577,20 @@ class ScheduleV2Event(BaseModel): TenancyConfig = PassThroughProp | None +class FunctionLogGroup(BaseModel): + RetentionInDays: PassThroughProp | None = passthrough_prop( + "sam-property-function-loggroup", + "RetentionInDays", + ["AWS::Logs::LogGroup", "Properties", "RetentionInDays"], + ) + DeletionPolicy: str | None = loggroup("DeletionPolicy") + LogGroupName: PassThroughProp | None = passthrough_prop( + "sam-property-function-loggroup", + "LogGroupName", + ["AWS::Logs::LogGroup", "Properties", "LogGroupName"], + ) + + class CapacityProviderConfig(BaseModel): Arn: SamIntrinsicable[str] = capacityproviderconfig("Arn") PerExecutionEnvironmentMaxConcurrency: SamIntrinsicable[int] | None = capacityproviderconfig( @@ -715,6 +730,7 @@ class Properties(BaseModel): "LoggingConfig", ["AWS::Lambda::Function", "Properties", "LoggingConfig"], ) + LogGroup: FunctionLogGroup | None = prop("LogGroup") RecursiveLoop: PassThroughProp | None = passthrough_prop( PROPERTIES_STEM, "RecursiveLoop", @@ -809,6 +825,7 @@ class Globals(BaseModel): "LoggingConfig", ["AWS::Lambda::Function", "Properties", "LoggingConfig"], ) + LogGroup: FunctionLogGroup | None = prop("LogGroup") RecursiveLoop: PassThroughProp | None = passthrough_prop( PROPERTIES_STEM, "RecursiveLoop", diff --git a/samtranslator/internal/schema_source/sam-docs.json b/samtranslator/internal/schema_source/sam-docs.json index 1242ad857..e40bd5048 100644 --- a/samtranslator/internal/schema_source/sam-docs.json +++ b/samtranslator/internal/schema_source/sam-docs.json @@ -890,7 +890,8 @@ "Tracing": "The string that specifies the function's X-Ray tracing mode. \n+ `Active` \u2013 Activates X-Ray tracing for the function.\n+ `Disabled` \u2013 Deactivates X-Ray for the function.\n+ `PassThrough` \u2013 Activates X-Ray tracing for the function. Sampling decision is delegated to the downstream services.\nIf specified as `Active` or `PassThrough` and the `Role` property is not set, AWS SAM adds the `arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess` policy to the Lambda execution role that it creates for you. \nFor more information about X-Ray, see [Using AWS Lambda with AWS X-Ray](https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html) in the *AWS Lambda Developer Guide*. \n*Valid values*: [`Active`\\$1`Disabled`\\$1`PassThrough`] \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is similar to the [`TracingConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-tracingconfig) property of an `AWS::Lambda::Function` resource.", "VersionDeletionPolicy": "Specifies the deletion policy for the Lambda version resource that is created when `AutoPublishAlias` is set. This controls whether the version resource is retained or deleted when the stack is deleted. \n*Valid values*: `Delete`, `Retain`, or `Snapshot` \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an CloudFormation equivalent. It sets the `DeletionPolicy` attribute on the generated `AWS::Lambda::Version` resource.", "VersionDescription": "Specifies the `Description` field that is added on the new Lambda version resource. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`Description`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-version.html#cfn-lambda-version-description) property of an `AWS::Lambda::Version` resource.", - "VpcConfig": "The configuration that enables this function to access private resources within your virtual private cloud (https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/VPC.html). \n*Type*: [VpcConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html) \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`VpcConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html) property of an `AWS::Lambda::Function` resource." + "VpcConfig": "The configuration that enables this function to access private resources within your virtual private cloud (https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/VPC.html). \n*Type*: [VpcConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html) \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`VpcConfig`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-vpcconfig.html) property of an `AWS::Lambda::Function` resource.", + "LogGroup": "The stack-managed Amazon CloudWatch Logs log group for the function. Specifying this property creates an `AWS::Logs::LogGroup` resource that is managed as part of the stack, so you can control its retention and have it deleted together with the stack. \nBy default, the log group uses the conventional `/aws/lambda/` name and no changes are made to the function's `LoggingConfig`. If `LogGroupName` is provided, the function's `LoggingConfig.LogGroup` is set so the function writes to the managed log group. \n*Type*: [LogGroup](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent." }, "sam-resource-graphqlapi": { "ApiKeys": "Create a unique key that can be used to perform GraphQL operations requiring an API key. \n*Type*: [ApiKeys](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-property-graphqlapi-apikeys.html) \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn\u2019t have an CloudFormation equivalent.", @@ -994,6 +995,11 @@ "SubnetIds": "A list of subnet IDs where the service provisions ENIs for VPC egress connectivity. \n*Type*: List \n*Required*: Yes \n*CloudFormation compatibility*: This property is passed directly to the [`SubnetIds`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-lambda-networkconnector-vpcegressconfiguration.html#cfn-lambda-networkconnector-vpcegressconfiguration-subnetids) property of `Configuration.VpcEgressConfiguration` of an `AWS::Lambda::NetworkConnector` resource.", "SecurityGroupIds": "A list of security group IDs to associate with the elastic network interfaces (ENIs). \n*Type*: List \n*Required*: Yes \n*CloudFormation compatibility*: This property is passed directly to the [`SecurityGroupIds`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-lambda-networkconnector-vpcegressconfiguration.html#cfn-lambda-networkconnector-vpcegressconfiguration-securitygroupids) property of `Configuration.VpcEgressConfiguration` of an `AWS::Lambda::NetworkConnector` resource.", "NetworkProtocol": "The network protocol for the VPC egress connection. \n*Valid values*: `IPv4`, `DualStack` \n*Type*: String \n*Required*: Yes \n*CloudFormation compatibility*: This property is passed directly to the [`NetworkProtocol`](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-properties-lambda-networkconnector-vpcegressconfiguration.html#cfn-lambda-networkconnector-vpcegressconfiguration-networkprotocol) property of `Configuration.VpcEgressConfiguration` of an `AWS::Lambda::NetworkConnector` resource." + }, + "sam-property-function-loggroup": { + "RetentionInDays": "The number of days to retain the log events in the managed log group. See [RetentionInDays](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-retentionindays). \n*Type*: Integer \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [RetentionInDays](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-retentionindays) property of an `AWS::Logs::LogGroup` resource.", + "DeletionPolicy": "Whether the managed log group is deleted or retained when it is removed from the stack. \n*Valid values*: `Delete`, `Retain` \n*Type*: String \n*Required*: No \n*Default*: `Delete` \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent. It sets the `DeletionPolicy` and `UpdateReplacePolicy` attributes of the generated `AWS::Logs::LogGroup` resource.", + "LogGroupName": "A custom name for the managed log group. When provided, the function's `LoggingConfig.LogGroup` is set to this value so the function writes to the managed log group. The name must not begin with `aws/`. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [LogGroupName](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-loggroupname) property of an `AWS::Logs::LogGroup` resource." } } -} \ No newline at end of file +} diff --git a/samtranslator/model/log.py b/samtranslator/model/log.py index e4b3ed4f9..8f647f64e 100644 --- a/samtranslator/model/log.py +++ b/samtranslator/model/log.py @@ -11,3 +11,17 @@ class SubscriptionFilter(Resource): } runtime_attrs = {"name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn")} + + +class LogGroup(Resource): + resource_type = "AWS::Logs::LogGroup" + property_types = { + "LogGroupName": GeneratedProperty(), + "RetentionInDays": GeneratedProperty(), + "KmsKeyId": GeneratedProperty(), + "DataProtectionPolicy": GeneratedProperty(), + "LogGroupClass": GeneratedProperty(), + "Tags": GeneratedProperty(), + } + + 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 14a9434bc..7ce2492fe 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -116,6 +116,7 @@ LambdaUrl, LambdaVersion, ) +from samtranslator.model.log import LogGroup as LogGroupResource from samtranslator.model.preferences.deployment_preference_collection import DeploymentPreferenceCollection from samtranslator.model.resource_policies import ResourcePolicies from samtranslator.model.role_utils import construct_role_for_resource @@ -201,6 +202,7 @@ class SamFunction(SamResourceMacro): "FunctionUrlConfig": PropertyType(False, IS_DICT), "RuntimeManagementConfig": PassThroughProperty(False), "LoggingConfig": PassThroughProperty(False), + "LogGroup": Property(False, IS_DICT), "RecursiveLoop": PassThroughProperty(False), "SourceKMSKeyArn": PassThroughProperty(False), "CapacityProviderConfig": PropertyType(False, IS_DICT), @@ -251,6 +253,7 @@ class SamFunction(SamResourceMacro): SnapStart: dict[str, Any] | None FunctionUrlConfig: dict[str, Any] | None LoggingConfig: dict[str, Any] | None + LogGroup: dict[str, Any] | None RecursiveLoop: str | None SourceKMSKeyArn: str | None CapacityProviderConfig: dict[str, Any] | None @@ -281,6 +284,8 @@ class SamFunction(SamResourceMacro): # EventConfig auto created SQS and SNS "DestinationTopic": SNSTopic.resource_type, "DestinationQueue": SQSQueue.resource_type, + # Stack-managed CloudWatch log group + "LogGroup": LogGroupResource.resource_type, } # Validation rules @@ -331,6 +336,11 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P lambda_function = self._construct_lambda_function(intrinsics_resolver) resources.append(lambda_function) + # A stack-managed log group must be constructed before the version, because a custom + # log group name mutates the function's LoggingConfig which feeds into the version hash. + if self.LogGroup is not None: + resources.append(self._construct_log_group(lambda_function)) + if self.ProvisionedConcurrencyConfig and not self.AutoPublishAlias: raise InvalidResourceException( self.logical_id, @@ -795,6 +805,57 @@ def _construct_lambda_function(self, intrinsics_resolver: IntrinsicsResolver) -> self._validate_package_type(lambda_function) return lambda_function + def _construct_log_group(self, lambda_function: LambdaFunction) -> LogGroupResource: + """Construct a stack-managed ``AWS::Logs::LogGroup`` for the function. + + By default (no ``LogGroupName``) the log group uses the conventional name + ``/aws/lambda/`` so that it coincides with the function's default + logging destination. In this case the function's ``LoggingConfig`` is left untouched + and no additional IAM permissions are required (the AWS managed policy + ``AWSLambdaBasicExecutionRole`` already grants log writes on all log groups). + + When a custom ``LogGroupName`` is provided, the function is explicitly bound to the + log group through its native ``LoggingConfig.LogGroup`` property. A managed execution + role can still write to it via ``AWSLambdaBasicExecutionRole``; a user-provided role + must include ``logs:CreateLogStream``/``logs:PutLogEvents`` for the custom log group. + + Making the log group part of the stack lets customers control its retention and + ensures it is deleted together with the stack (see GitHub issue #1216). + """ + config = sam_expect(self.LogGroup, self.logical_id, "LogGroup").to_be_a_map() + + deletion_policy = config.get("DeletionPolicy", "Delete") + if deletion_policy not in ("Delete", "Retain"): + raise InvalidResourceException( + self.logical_id, + "'LogGroup.DeletionPolicy' must be one of 'Delete' or 'Retain'.", + ) + + attributes = self.get_passthrough_resource_attributes() or {} + attributes["DeletionPolicy"] = deletion_policy + attributes["UpdateReplacePolicy"] = deletion_policy + + log_group = LogGroupResource(logical_id=f"{self.logical_id}LogGroup", attributes=attributes) + + custom_name = config.get("LogGroupName") + if custom_name is not None: + # Mechanism 2: custom log group name. Bind the function to the log group via its + # native LoggingConfig so Lambda writes to the stack-managed group. + log_group.LogGroupName = custom_name + logging_config = dict(lambda_function.LoggingConfig or {}) + logging_config["LogGroup"] = custom_name + lambda_function.LoggingConfig = logging_config + else: + # Mechanism 1: conventional /aws/lambda/. Fn::Sub resolves the + # function's Ref (its physical name, even when auto-generated), matching Lambda's + # default logging destination without touching LoggingConfig or IAM. + log_group.LogGroupName = fnSub(f"/aws/lambda/${{{lambda_function.logical_id}}}") + + if "RetentionInDays" in config: + log_group.RetentionInDays = config["RetentionInDays"] + + return log_group + def _transform_capacity_provider_config(self) -> dict[str, Any]: """ Transform SAM CapacityProviderConfig to CloudFormation format. diff --git a/samtranslator/plugins/globals/globals.py b/samtranslator/plugins/globals/globals.py index e1603e547..16e261edc 100644 --- a/samtranslator/plugins/globals/globals.py +++ b/samtranslator/plugins/globals/globals.py @@ -54,6 +54,7 @@ class Globals: "FunctionUrlConfig", "RuntimeManagementConfig", "LoggingConfig", + "LogGroup", "RecursiveLoop", "SourceKMSKeyArn", "TenancyConfig", diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index 98f334781..efcd257f3 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -366282,6 +366282,28 @@ "title": "Function", "type": "object" }, + "FunctionLogGroup": { + "additionalProperties": false, + "properties": { + "DeletionPolicy": { + "markdownDescription": "Whether the managed log group is deleted or retained when it is removed from the stack. \n*Valid values*: `Delete`, `Retain` \n*Type*: String \n*Required*: No \n*Default*: `Delete` \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent. It sets the `DeletionPolicy` and `UpdateReplacePolicy` attributes of the generated `AWS::Logs::LogGroup` resource.", + "title": "DeletionPolicy", + "type": "string" + }, + "LogGroupName": { + "markdownDescription": "A custom name for the managed log group. When provided, the function's `LoggingConfig.LogGroup` is set to this value so the function writes to the managed log group. The name must not begin with `aws/`. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [LogGroupName](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-loggroupname) property of an `AWS::Logs::LogGroup` resource.", + "title": "LogGroupName", + "type": "string" + }, + "RetentionInDays": { + "markdownDescription": "The number of days to retain the log events in the managed log group. See [RetentionInDays](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-retentionindays). \n*Type*: Integer \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [RetentionInDays](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-retentionindays) property of an `AWS::Logs::LogGroup` resource.", + "title": "RetentionInDays", + "type": "number" + } + }, + "title": "FunctionLogGroup", + "type": "object" + }, "FunctionUrlConfig": { "additionalProperties": false, "properties": { @@ -370823,6 +370845,15 @@ "markdownDescription": "The list of `LayerVersion` ARNs that this function should use. The order specified here is the order in which they will be imported when running the Lambda function. The version is either a full ARN including the version or a reference to a LayerVersion resource. For example, a reference to a `LayerVersion` will be `!Ref MyLayer` while a full ARN including the version will be `arn:aws:lambda:region:account-id:layer:layer-name:version`. \n*Type*: List \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`Layers`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-layers) property of an `AWS::Lambda::Function` resource.", "title": "Layers" }, + "LogGroup": { + "allOf": [ + { + "$ref": "#/definitions/FunctionLogGroup" + } + ], + "markdownDescription": "The stack-managed Amazon CloudWatch Logs log group for the function. Specifying this property creates an `AWS::Logs::LogGroup` resource that is managed as part of the stack, so you can control its retention and have it deleted together with the stack. \nBy default, the log group uses the conventional `/aws/lambda/` name and no changes are made to the function's `LoggingConfig`. If `LogGroupName` is provided, the function's `LoggingConfig.LogGroup` is set so the function writes to the managed log group. \n*Type*: [LogGroup](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent.", + "title": "LogGroup" + }, "LoggingConfig": { "$ref": "#/definitions/AWS::Lambda::Function.LoggingConfig", "markdownDescription": "The function's Amazon CloudWatch Logs configuration settings. \n*Type*: [LoggingConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-loggingconfig.html) \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig) property of an `AWS::Lambda::Function` resource.", @@ -371259,6 +371290,15 @@ "markdownDescription": "The list of `LayerVersion` ARNs that this function should use. The order specified here is the order in which they will be imported when running the Lambda function. The version is either a full ARN including the version or a reference to a LayerVersion resource. For example, a reference to a `LayerVersion` will be `!Ref MyLayer` while a full ARN including the version will be `arn:aws:lambda:region:account-id:layer:layer-name:version`. \n*Type*: List \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`Layers`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-layers) property of an `AWS::Lambda::Function` resource.", "title": "Layers" }, + "LogGroup": { + "allOf": [ + { + "$ref": "#/definitions/FunctionLogGroup" + } + ], + "markdownDescription": "The stack-managed Amazon CloudWatch Logs log group for the function. Specifying this property creates an `AWS::Logs::LogGroup` resource that is managed as part of the stack, so you can control its retention and have it deleted together with the stack. \nBy default, the log group uses the conventional `/aws/lambda/` name and no changes are made to the function's `LoggingConfig`. If `LogGroupName` is provided, the function's `LoggingConfig.LogGroup` is set so the function writes to the managed log group. \n*Type*: [LogGroup](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent.", + "title": "LogGroup" + }, "LoggingConfig": { "$ref": "#/definitions/AWS::Lambda::Function.LoggingConfig", "markdownDescription": "The function's Amazon CloudWatch Logs configuration settings. \n*Type*: [LoggingConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-loggingconfig.html) \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig) property of an `AWS::Lambda::Function` resource.", diff --git a/schema_source/sam.schema.json b/schema_source/sam.schema.json index 11cb241d9..06c9832ea 100644 --- a/schema_source/sam.schema.json +++ b/schema_source/sam.schema.json @@ -1774,6 +1774,56 @@ "title": "Function", "type": "object" }, + "FunctionLogGroup": { + "additionalProperties": false, + "properties": { + "DeletionPolicy": { + "markdownDescription": "Whether the managed log group is deleted or retained when it is removed from the stack. \n*Valid values*: `Delete`, `Retain` \n*Type*: String \n*Required*: No \n*Default*: `Delete` \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent. It sets the `DeletionPolicy` and `UpdateReplacePolicy` attributes of the generated `AWS::Logs::LogGroup` resource.", + "title": "DeletionPolicy", + "type": "string" + }, + "LogGroupName": { + "__samPassThrough": { + "markdownDescriptionOverride": "A custom name for the managed log group. When provided, the function's `LoggingConfig.LogGroup` is set to this value so the function writes to the managed log group. The name must not begin with `aws/`. \n*Type*: String \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [LogGroupName](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-loggroupname) property of an `AWS::Logs::LogGroup` resource.", + "schemaPath": [ + "definitions", + "AWS::Logs::LogGroup", + "properties", + "Properties", + "properties", + "LogGroupName" + ] + }, + "allOf": [ + { + "$ref": "#/definitions/PassThroughProp" + } + ], + "title": "LogGroupName" + }, + "RetentionInDays": { + "__samPassThrough": { + "markdownDescriptionOverride": "The number of days to retain the log events in the managed log group. See [RetentionInDays](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-retentionindays). \n*Type*: Integer \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [RetentionInDays](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html#cfn-logs-loggroup-retentionindays) property of an `AWS::Logs::LogGroup` resource.", + "schemaPath": [ + "definitions", + "AWS::Logs::LogGroup", + "properties", + "Properties", + "properties", + "RetentionInDays" + ] + }, + "allOf": [ + { + "$ref": "#/definitions/PassThroughProp" + } + ], + "title": "RetentionInDays" + } + }, + "title": "FunctionLogGroup", + "type": "object" + }, "FunctionUrlConfig": { "additionalProperties": false, "properties": { @@ -7073,6 +7123,15 @@ "markdownDescription": "The list of `LayerVersion` ARNs that this function should use. The order specified here is the order in which they will be imported when running the Lambda function. The version is either a full ARN including the version or a reference to a LayerVersion resource. For example, a reference to a `LayerVersion` will be `!Ref MyLayer` while a full ARN including the version will be `arn:aws:lambda:region:account-id:layer:layer-name:version`. \n*Type*: List \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`Layers`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-layers) property of an `AWS::Lambda::Function` resource.", "title": "Layers" }, + "LogGroup": { + "allOf": [ + { + "$ref": "#/definitions/FunctionLogGroup" + } + ], + "markdownDescription": "The stack-managed Amazon CloudWatch Logs log group for the function. Specifying this property creates an `AWS::Logs::LogGroup` resource that is managed as part of the stack, so you can control its retention and have it deleted together with the stack. \nBy default, the log group uses the conventional `/aws/lambda/` name and no changes are made to the function's `LoggingConfig`. If `LogGroupName` is provided, the function's `LoggingConfig.LogGroup` is set so the function writes to the managed log group. \n*Type*: [LogGroup](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent.", + "title": "LogGroup" + }, "LoggingConfig": { "__samPassThrough": { "markdownDescriptionOverride": "The function's Amazon CloudWatch Logs configuration settings. \n*Type*: [LoggingConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-loggingconfig.html) \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig) property of an `AWS::Lambda::Function` resource.", @@ -7782,6 +7841,15 @@ "markdownDescription": "The list of `LayerVersion` ARNs that this function should use. The order specified here is the order in which they will be imported when running the Lambda function. The version is either a full ARN including the version or a reference to a LayerVersion resource. For example, a reference to a `LayerVersion` will be `!Ref MyLayer` while a full ARN including the version will be `arn:aws:lambda:region:account-id:layer:layer-name:version`. \n*Type*: List \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [`Layers`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-layers) property of an `AWS::Lambda::Function` resource.", "title": "Layers" }, + "LogGroup": { + "allOf": [ + { + "$ref": "#/definitions/FunctionLogGroup" + } + ], + "markdownDescription": "The stack-managed Amazon CloudWatch Logs log group for the function. Specifying this property creates an `AWS::Logs::LogGroup` resource that is managed as part of the stack, so you can control its retention and have it deleted together with the stack. \nBy default, the log group uses the conventional `/aws/lambda/` name and no changes are made to the function's `LoggingConfig`. If `LogGroupName` is provided, the function's `LoggingConfig.LogGroup` is set so the function writes to the managed log group. \n*Type*: [LogGroup](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html) \n*Required*: No \n*CloudFormation compatibility*: This property is unique to AWS SAM and doesn't have an AWS CloudFormation equivalent.", + "title": "LogGroup" + }, "LoggingConfig": { "__samPassThrough": { "markdownDescriptionOverride": "The function's Amazon CloudWatch Logs configuration settings. \n*Type*: [LoggingConfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-loggingconfig.html) \n*Required*: No \n*CloudFormation compatibility*: This property is passed directly to the [https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig) property of an `AWS::Lambda::Function` resource.", diff --git a/tests/model/test_log.py b/tests/model/test_log.py new file mode 100644 index 000000000..8897468cc --- /dev/null +++ b/tests/model/test_log.py @@ -0,0 +1,52 @@ +from unittest import TestCase + +from samtranslator.model.log import LogGroup, SubscriptionFilter + + +class TestLogGroup(TestCase): + def test_resource_type(self): + log_group = LogGroup("MyFunctionLogGroup") + self.assertEqual(log_group.resource_type, "AWS::Logs::LogGroup") + + def test_to_dict_with_properties(self): + log_group = LogGroup( + "MyFunctionLogGroup", + attributes={"DeletionPolicy": "Retain", "UpdateReplacePolicy": "Retain"}, + ) + log_group.LogGroupName = {"Fn::Sub": "/aws/lambda/${MyFunction}"} + log_group.RetentionInDays = 7 + + self.assertEqual( + log_group.to_dict(), + { + "MyFunctionLogGroup": { + "Type": "AWS::Logs::LogGroup", + "DeletionPolicy": "Retain", + "UpdateReplacePolicy": "Retain", + "Properties": { + "LogGroupName": {"Fn::Sub": "/aws/lambda/${MyFunction}"}, + "RetentionInDays": 7, + }, + } + }, + ) + + def test_runtime_attrs(self): + log_group = LogGroup("MyFunctionLogGroup") + self.assertEqual(log_group.get_runtime_attr("name"), {"Ref": "MyFunctionLogGroup"}) + self.assertEqual( + log_group.get_runtime_attr("arn"), + {"Fn::GetAtt": ["MyFunctionLogGroup", "Arn"]}, + ) + + def test_supported_properties(self): + # LogGroup and SubscriptionFilter live in the same module; make sure the + # LogGroup exposes the CloudFormation properties we rely on. + self.assertIn("LogGroupName", LogGroup.property_types) + self.assertIn("RetentionInDays", LogGroup.property_types) + self.assertIn("Tags", LogGroup.property_types) + self.assertIn("KmsKeyId", LogGroup.property_types) + self.assertIn("DataProtectionPolicy", LogGroup.property_types) + self.assertIn("LogGroupClass", LogGroup.property_types) + # Sanity check the neighbouring resource is untouched. + self.assertEqual(SubscriptionFilter.resource_type, "AWS::Logs::SubscriptionFilter") diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index a51d9c5f9..fb8c4d058 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -9,6 +9,7 @@ from samtranslator.model.apigatewayv2 import ApiGatewayV2HttpApi from samtranslator.model.iam import IAMRole from samtranslator.model.lambda_ import LambdaFunction, LambdaLayerVersion, LambdaPermission, LambdaUrl, LambdaVersion +from samtranslator.model.log import LogGroup from samtranslator.model.packagetype import IMAGE, ZIP from samtranslator.model.sam_resources import ( SamApi, @@ -1023,3 +1024,87 @@ def test_managed_policy_name_within_intrinsic_if_else(self): self.assertEqual( iamRoles[0].ManagedPolicyArns[1]["Fn::If"][2], self.kwargs["managed_policy_map"][managedPolicyName] ) + + +class TestLogGroup(TestCase): + kwargs = { + "intrinsics_resolver": IntrinsicsResolver({}), + "event_resources": [], + "managed_policy_map": {"foo": "bar"}, + "resource_resolver": ResourceResolver({}), + } + + def _make_function(self): + function = SamFunction("MyFunction") + function.InlineCode = "hello world" + function.Runtime = "python3.12" + function.Handler = "index.handler" + return function + + def _transform(self, function): + resources = function.to_cloudformation(**self.kwargs) + functions = [r for r in resources if isinstance(r, LambdaFunction)] + log_groups = [r for r in resources if isinstance(r, LogGroup)] + return functions, log_groups + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_no_log_group_by_default(self): + # Backward compatibility: no LogGroup property means no log group generated. + function = self._make_function() + _, log_groups = self._transform(function) + self.assertEqual(log_groups, []) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_default_log_group_name_and_deletion_policy(self): + function = self._make_function() + function.LogGroup = {"RetentionInDays": 7} + functions, log_groups = self._transform(function) + + self.assertEqual(len(log_groups), 1) + log_group = log_groups[0] + self.assertEqual(log_group.logical_id, "MyFunctionLogGroup") + self.assertEqual(log_group.LogGroupName, {"Fn::Sub": "/aws/lambda/${MyFunction}"}) + self.assertEqual(log_group.RetentionInDays, 7) + + log_group_dict = log_group.to_dict()["MyFunctionLogGroup"] + self.assertEqual(log_group_dict["DeletionPolicy"], "Delete") + self.assertEqual(log_group_dict["UpdateReplacePolicy"], "Delete") + + # Mechanism 1 must not touch the function's LoggingConfig. + self.assertIsNone(functions[0].LoggingConfig) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_deletion_policy_retain(self): + function = self._make_function() + function.LogGroup = {"DeletionPolicy": "Retain"} + _, log_groups = self._transform(function) + + log_group_dict = log_groups[0].to_dict()["MyFunctionLogGroup"] + self.assertEqual(log_group_dict["DeletionPolicy"], "Retain") + self.assertEqual(log_group_dict["UpdateReplacePolicy"], "Retain") + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_custom_log_group_name_wires_logging_config(self): + function = self._make_function() + function.LoggingConfig = {"LogFormat": "JSON"} + function.LogGroup = {"LogGroupName": "my-custom-group", "RetentionInDays": 14} + functions, log_groups = self._transform(function) + + log_group = log_groups[0] + self.assertEqual(log_group.LogGroupName, "my-custom-group") + self.assertEqual(log_group.RetentionInDays, 14) + + # Mechanism 2 binds the function to the managed log group without dropping + # existing LoggingConfig settings. + self.assertEqual( + functions[0].LoggingConfig, + {"LogFormat": "JSON", "LogGroup": "my-custom-group"}, + ) + + @patch("boto3.session.Session.region_name", "ap-southeast-1") + def test_invalid_deletion_policy_raises(self): + function = self._make_function() + function.LogGroup = {"DeletionPolicy": "Snapshot"} + with pytest.raises(InvalidResourceException) as ctx: + function.to_cloudformation(**self.kwargs) + self.assertIn("LogGroup.DeletionPolicy", str(ctx.value)) diff --git a/tests/translator/input/error_function_with_invalid_log_group_deletion_policy.yaml b/tests/translator/input/error_function_with_invalid_log_group_deletion_policy.yaml new file mode 100644 index 000000000..2cc52fb5a --- /dev/null +++ b/tests/translator/input/error_function_with_invalid_log_group_deletion_policy.yaml @@ -0,0 +1,10 @@ +Resources: + InvalidLogGroupFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + LogGroup: + RetentionInDays: 7 + DeletionPolicy: Snapshot diff --git a/tests/translator/input/function_with_log_group.yaml b/tests/translator/input/function_with_log_group.yaml new file mode 100644 index 000000000..1680c58a0 --- /dev/null +++ b/tests/translator/input/function_with_log_group.yaml @@ -0,0 +1,24 @@ +Resources: + RetentionFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + LogGroup: + RetentionInDays: 7 + + DefaultLogGroupFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + LogGroup: {} + + NoLogGroupFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 diff --git a/tests/translator/input/function_with_log_group_condition.yaml b/tests/translator/input/function_with_log_group_condition.yaml new file mode 100644 index 000000000..9790a9dbb --- /dev/null +++ b/tests/translator/input/function_with_log_group_condition.yaml @@ -0,0 +1,21 @@ +Parameters: + CreateResources: + Type: String + Default: 'true' + +Conditions: + ShouldCreate: + Fn::Equals: + - !Ref CreateResources + - 'true' + +Resources: + ConditionalLogGroupFunction: + Type: AWS::Serverless::Function + Condition: ShouldCreate + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + LogGroup: + RetentionInDays: 30 diff --git a/tests/translator/input/function_with_log_group_custom_name.yaml b/tests/translator/input/function_with_log_group_custom_name.yaml new file mode 100644 index 000000000..e4d149fcc --- /dev/null +++ b/tests/translator/input/function_with_log_group_custom_name.yaml @@ -0,0 +1,12 @@ +Resources: + CustomLogGroupFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + LoggingConfig: + LogFormat: JSON + LogGroup: + RetentionInDays: 14 + LogGroupName: my-custom-log-group diff --git a/tests/translator/input/function_with_log_group_globals.yaml b/tests/translator/input/function_with_log_group_globals.yaml new file mode 100644 index 000000000..55dc83b72 --- /dev/null +++ b/tests/translator/input/function_with_log_group_globals.yaml @@ -0,0 +1,22 @@ +Globals: + Function: + LogGroup: + RetentionInDays: 30 + DeletionPolicy: Retain + +Resources: + InheritLogGroupFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + + OverrideLogGroupFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + LogGroup: + RetentionInDays: 7 diff --git a/tests/translator/input/function_with_log_group_retain.yaml b/tests/translator/input/function_with_log_group_retain.yaml new file mode 100644 index 000000000..187be6aab --- /dev/null +++ b/tests/translator/input/function_with_log_group_retain.yaml @@ -0,0 +1,10 @@ +Resources: + RetainLogGroupFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.12 + LogGroup: + RetentionInDays: 90 + DeletionPolicy: Retain diff --git a/tests/translator/output/aws-cn/function_with_log_group.json b/tests/translator/output/aws-cn/function_with_log_group.json new file mode 100644 index 000000000..3f8dcf9c5 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_log_group.json @@ -0,0 +1,184 @@ +{ + "Resources": { + "DefaultLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "DefaultLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "DefaultLogGroupFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${DefaultLogGroupFunction}" + } + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "DefaultLogGroupFunctionRole": { + "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" + }, + "NoLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "NoLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "NoLogGroupFunctionRole": { + "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" + }, + "RetentionFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "RetentionFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RetentionFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${RetentionFunction}" + }, + "RetentionInDays": 7 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "RetentionFunctionRole": { + "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/function_with_log_group_condition.json b/tests/translator/output/aws-cn/function_with_log_group_condition.json new file mode 100644 index 000000000..a591b1a22 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_log_group_condition.json @@ -0,0 +1,87 @@ +{ + "Conditions": { + "ShouldCreate": { + "Fn::Equals": [ + { + "Ref": "CreateResources" + }, + "true" + ] + } + }, + "Parameters": { + "CreateResources": { + "Default": "true", + "Type": "String" + } + }, + "Resources": { + "ConditionalLogGroupFunction": { + "Condition": "ShouldCreate", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "ConditionalLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "ConditionalLogGroupFunctionLogGroup": { + "Condition": "ShouldCreate", + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${ConditionalLogGroupFunction}" + }, + "RetentionInDays": 30 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "ConditionalLogGroupFunctionRole": { + "Condition": "ShouldCreate", + "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/function_with_log_group_custom_name.json b/tests/translator/output/aws-cn/function_with_log_group_custom_name.json new file mode 100644 index 000000000..3010b426e --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_log_group_custom_name.json @@ -0,0 +1,70 @@ +{ + "Resources": { + "CustomLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "LoggingConfig": { + "LogFormat": "JSON", + "LogGroup": "my-custom-log-group" + }, + "Role": { + "Fn::GetAtt": [ + "CustomLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "CustomLogGroupFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": "my-custom-log-group", + "RetentionInDays": 14 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "CustomLogGroupFunctionRole": { + "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/function_with_log_group_globals.json b/tests/translator/output/aws-cn/function_with_log_group_globals.json new file mode 100644 index 000000000..d06d00b02 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_log_group_globals.json @@ -0,0 +1,132 @@ +{ + "Resources": { + "InheritLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "InheritLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InheritLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${InheritLogGroupFunction}" + }, + "RetentionInDays": 30 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "InheritLogGroupFunctionRole": { + "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" + }, + "OverrideLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "OverrideLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "OverrideLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${OverrideLogGroupFunction}" + }, + "RetentionInDays": 7 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "OverrideLogGroupFunctionRole": { + "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/function_with_log_group_retain.json b/tests/translator/output/aws-cn/function_with_log_group_retain.json new file mode 100644 index 000000000..1d2906521 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_log_group_retain.json @@ -0,0 +1,68 @@ +{ + "Resources": { + "RetainLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "RetainLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RetainLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${RetainLogGroupFunction}" + }, + "RetentionInDays": 90 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "RetainLogGroupFunctionRole": { + "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-us-gov/function_with_log_group.json b/tests/translator/output/aws-us-gov/function_with_log_group.json new file mode 100644 index 000000000..f2c3fb630 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_log_group.json @@ -0,0 +1,184 @@ +{ + "Resources": { + "DefaultLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "DefaultLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "DefaultLogGroupFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${DefaultLogGroupFunction}" + } + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "DefaultLogGroupFunctionRole": { + "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" + }, + "NoLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "NoLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "NoLogGroupFunctionRole": { + "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" + }, + "RetentionFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "RetentionFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RetentionFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${RetentionFunction}" + }, + "RetentionInDays": 7 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "RetentionFunctionRole": { + "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/function_with_log_group_condition.json b/tests/translator/output/aws-us-gov/function_with_log_group_condition.json new file mode 100644 index 000000000..a6b3bdfe4 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_log_group_condition.json @@ -0,0 +1,87 @@ +{ + "Conditions": { + "ShouldCreate": { + "Fn::Equals": [ + { + "Ref": "CreateResources" + }, + "true" + ] + } + }, + "Parameters": { + "CreateResources": { + "Default": "true", + "Type": "String" + } + }, + "Resources": { + "ConditionalLogGroupFunction": { + "Condition": "ShouldCreate", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "ConditionalLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "ConditionalLogGroupFunctionLogGroup": { + "Condition": "ShouldCreate", + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${ConditionalLogGroupFunction}" + }, + "RetentionInDays": 30 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "ConditionalLogGroupFunctionRole": { + "Condition": "ShouldCreate", + "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/function_with_log_group_custom_name.json b/tests/translator/output/aws-us-gov/function_with_log_group_custom_name.json new file mode 100644 index 000000000..ed5d6ca94 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_log_group_custom_name.json @@ -0,0 +1,70 @@ +{ + "Resources": { + "CustomLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "LoggingConfig": { + "LogFormat": "JSON", + "LogGroup": "my-custom-log-group" + }, + "Role": { + "Fn::GetAtt": [ + "CustomLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "CustomLogGroupFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": "my-custom-log-group", + "RetentionInDays": 14 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "CustomLogGroupFunctionRole": { + "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/function_with_log_group_globals.json b/tests/translator/output/aws-us-gov/function_with_log_group_globals.json new file mode 100644 index 000000000..cc3bce02f --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_log_group_globals.json @@ -0,0 +1,132 @@ +{ + "Resources": { + "InheritLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "InheritLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InheritLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${InheritLogGroupFunction}" + }, + "RetentionInDays": 30 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "InheritLogGroupFunctionRole": { + "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" + }, + "OverrideLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "OverrideLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "OverrideLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${OverrideLogGroupFunction}" + }, + "RetentionInDays": 7 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "OverrideLogGroupFunctionRole": { + "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/function_with_log_group_retain.json b/tests/translator/output/aws-us-gov/function_with_log_group_retain.json new file mode 100644 index 000000000..b34635f36 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_log_group_retain.json @@ -0,0 +1,68 @@ +{ + "Resources": { + "RetainLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "RetainLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RetainLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${RetainLogGroupFunction}" + }, + "RetentionInDays": 90 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "RetainLogGroupFunctionRole": { + "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/error_function_with_invalid_log_group_deletion_policy.json b/tests/translator/output/error_function_with_invalid_log_group_deletion_policy.json new file mode 100644 index 000000000..223f326ec --- /dev/null +++ b/tests/translator/output/error_function_with_invalid_log_group_deletion_policy.json @@ -0,0 +1,9 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 1. ", + "Resource with id [InvalidLogGroupFunction] is invalid. ", + "'LogGroup.DeletionPolicy' must be one of 'Delete' or 'Retain'." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [InvalidLogGroupFunction] is invalid. 'LogGroup.DeletionPolicy' must be one of 'Delete' or 'Retain'." +} diff --git a/tests/translator/output/error_globals_unsupported_property.json b/tests/translator/output/error_globals_unsupported_property.json index b7aeb3c83..cd0118165 100644 --- a/tests/translator/output/error_globals_unsupported_property.json +++ b/tests/translator/output/error_globals_unsupported_property.json @@ -4,7 +4,7 @@ "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', 'PropagateTags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'AutoPublishAliasAllProperties', 'Layers', 'DeploymentPreference', 'RolePath', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn', 'Architectures', 'SnapStart', 'EphemeralStorage', 'FunctionUrlConfig', 'RuntimeManagementConfig', 'LoggingConfig', 'RecursiveLoop', 'SourceKMSKeyArn', 'TenancyConfig', 'DurableConfig', 'CapacityProviderConfig', 'FunctionScalingConfig', 'PublishToLatestPublished', 'VersionDeletionPolicy']" + "Must be one of the following values - ['Handler', 'Runtime', 'CodeUri', 'DeadLetterQueue', 'Description', 'MemorySize', 'Timeout', 'VpcConfig', 'Environment', 'Tags', 'PropagateTags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'AutoPublishAliasAllProperties', 'Layers', 'DeploymentPreference', 'RolePath', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn', 'Architectures', 'SnapStart', 'EphemeralStorage', 'FunctionUrlConfig', 'RuntimeManagementConfig', 'LoggingConfig', 'LogGroup', 'RecursiveLoop', 'SourceKMSKeyArn', 'TenancyConfig', 'DurableConfig', 'CapacityProviderConfig', 'FunctionScalingConfig', 'PublishToLatestPublished', 'VersionDeletionPolicy']" ], - "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', 'PropagateTags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'AutoPublishAliasAllProperties', 'Layers', 'DeploymentPreference', 'RolePath', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn', 'Architectures', 'SnapStart', 'EphemeralStorage', 'FunctionUrlConfig', 'RuntimeManagementConfig', 'LoggingConfig', 'RecursiveLoop', 'SourceKMSKeyArn', 'TenancyConfig', 'DurableConfig', 'CapacityProviderConfig', 'FunctionScalingConfig', 'PublishToLatestPublished', 'VersionDeletionPolicy']" + "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', 'PropagateTags', 'Tracing', 'KmsKeyArn', 'AutoPublishAlias', 'AutoPublishAliasAllProperties', 'Layers', 'DeploymentPreference', 'RolePath', 'PermissionsBoundary', 'ReservedConcurrentExecutions', 'ProvisionedConcurrencyConfig', 'AssumeRolePolicyDocument', 'EventInvokeConfig', 'FileSystemConfigs', 'CodeSigningConfigArn', 'Architectures', 'SnapStart', 'EphemeralStorage', 'FunctionUrlConfig', 'RuntimeManagementConfig', 'LoggingConfig', 'LogGroup', 'RecursiveLoop', 'SourceKMSKeyArn', 'TenancyConfig', 'DurableConfig', 'CapacityProviderConfig', 'FunctionScalingConfig', 'PublishToLatestPublished', 'VersionDeletionPolicy']" } diff --git a/tests/translator/output/function_with_log_group.json b/tests/translator/output/function_with_log_group.json new file mode 100644 index 000000000..160d97211 --- /dev/null +++ b/tests/translator/output/function_with_log_group.json @@ -0,0 +1,184 @@ +{ + "Resources": { + "DefaultLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "DefaultLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "DefaultLogGroupFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${DefaultLogGroupFunction}" + } + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "DefaultLogGroupFunctionRole": { + "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" + }, + "NoLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "NoLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "NoLogGroupFunctionRole": { + "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" + }, + "RetentionFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "RetentionFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RetentionFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${RetentionFunction}" + }, + "RetentionInDays": 7 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "RetentionFunctionRole": { + "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/function_with_log_group_condition.json b/tests/translator/output/function_with_log_group_condition.json new file mode 100644 index 000000000..1f013ea01 --- /dev/null +++ b/tests/translator/output/function_with_log_group_condition.json @@ -0,0 +1,87 @@ +{ + "Conditions": { + "ShouldCreate": { + "Fn::Equals": [ + { + "Ref": "CreateResources" + }, + "true" + ] + } + }, + "Parameters": { + "CreateResources": { + "Default": "true", + "Type": "String" + } + }, + "Resources": { + "ConditionalLogGroupFunction": { + "Condition": "ShouldCreate", + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "ConditionalLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "ConditionalLogGroupFunctionLogGroup": { + "Condition": "ShouldCreate", + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${ConditionalLogGroupFunction}" + }, + "RetentionInDays": 30 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "ConditionalLogGroupFunctionRole": { + "Condition": "ShouldCreate", + "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/function_with_log_group_custom_name.json b/tests/translator/output/function_with_log_group_custom_name.json new file mode 100644 index 000000000..da8264560 --- /dev/null +++ b/tests/translator/output/function_with_log_group_custom_name.json @@ -0,0 +1,70 @@ +{ + "Resources": { + "CustomLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "LoggingConfig": { + "LogFormat": "JSON", + "LogGroup": "my-custom-log-group" + }, + "Role": { + "Fn::GetAtt": [ + "CustomLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "CustomLogGroupFunctionLogGroup": { + "DeletionPolicy": "Delete", + "Properties": { + "LogGroupName": "my-custom-log-group", + "RetentionInDays": 14 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Delete" + }, + "CustomLogGroupFunctionRole": { + "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/function_with_log_group_globals.json b/tests/translator/output/function_with_log_group_globals.json new file mode 100644 index 000000000..4a087dd01 --- /dev/null +++ b/tests/translator/output/function_with_log_group_globals.json @@ -0,0 +1,132 @@ +{ + "Resources": { + "InheritLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "InheritLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "InheritLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${InheritLogGroupFunction}" + }, + "RetentionInDays": 30 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "InheritLogGroupFunctionRole": { + "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" + }, + "OverrideLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "OverrideLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "OverrideLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${OverrideLogGroupFunction}" + }, + "RetentionInDays": 7 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "OverrideLogGroupFunctionRole": { + "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/function_with_log_group_retain.json b/tests/translator/output/function_with_log_group_retain.json new file mode 100644 index 000000000..1775666ed --- /dev/null +++ b/tests/translator/output/function_with_log_group_retain.json @@ -0,0 +1,68 @@ +{ + "Resources": { + "RetainLogGroupFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::GetAtt": [ + "RetainLogGroupFunctionRole", + "Arn" + ] + }, + "Runtime": "python3.12", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "RetainLogGroupFunctionLogGroup": { + "DeletionPolicy": "Retain", + "Properties": { + "LogGroupName": { + "Fn::Sub": "/aws/lambda/${RetainLogGroupFunction}" + }, + "RetentionInDays": 90 + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain" + }, + "RetainLogGroupFunctionRole": { + "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/test_function_resources.py b/tests/translator/test_function_resources.py index 73afe8b17..f98a4760c 100644 --- a/tests/translator/test_function_resources.py +++ b/tests/translator/test_function_resources.py @@ -888,8 +888,9 @@ def _make_deployment_preference_collection(self): class TestSupportedResourceReferences(TestCase): def test_must_not_break_support(self): func = SamFunction("LogicalId") - self.assertEqual(4, len(func.referable_properties)) + self.assertEqual(5, len(func.referable_properties)) self.assertEqual(func.referable_properties["Alias"], "AWS::Lambda::Alias") self.assertEqual(func.referable_properties["Version"], "AWS::Lambda::Version") self.assertEqual(func.referable_properties["DestinationTopic"], "AWS::SNS::Topic") self.assertEqual(func.referable_properties["DestinationQueue"], "AWS::SQS::Queue") + self.assertEqual(func.referable_properties["LogGroup"], "AWS::Logs::LogGroup")