Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions samtranslator/internal/schema_source/aws_serverless_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions samtranslator/internal/schema_source/sam-docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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/<function-name>` 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.",
Expand Down Expand Up @@ -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."
}
}
}
}
14 changes: 14 additions & 0 deletions samtranslator/model/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")}
61 changes: 61 additions & 0 deletions samtranslator/model/sam_resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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/<function name>`` 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/<function name>. 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.
Expand Down
1 change: 1 addition & 0 deletions samtranslator/plugins/globals/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class Globals:
"FunctionUrlConfig",
"RuntimeManagementConfig",
"LoggingConfig",
"LogGroup",
"RecursiveLoop",
"SourceKMSKeyArn",
"TenancyConfig",
Expand Down
Loading