class DynamoDBTable(Resource): resource_type = 'AWS::DynamoDB::Table' property_types = { 'AttributeDefinitions': PropertyType(True, list_of(is_type(dict))), 'GlobalSecondaryIndexes': PropertyType(False, list_of(is_type(dict))), 'KeySchema': PropertyType(False, list_of(is_type(dict))), 'LocalSecondaryIndexes': PropertyType(False, list_of(is_type(dict))), 'ProvisionedThroughput': PropertyType(False, dict_of(is_str(), one_of(is_type(int), is_type(dict)))), 'StreamSpecification': PropertyType(False, is_type(dict)), 'TableName': PropertyType(False, one_of(is_str(), is_type(dict))), 'Tags': PropertyType(False, list_of(is_type(dict))), 'SSESpecification': PropertyType(False, is_type(dict)), 'BillingMode': PropertyType(False, is_str()) } runtime_attrs = { "name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn"), "stream_arn": lambda self: fnGetAtt(self.logical_id, "StreamArn") }
class SNSSubscription(Resource): resource_type = 'AWS::SNS::Subscription' property_types = { 'Endpoint': PropertyType(True, is_str()), 'Protocol': PropertyType(True, is_str()), 'TopicArn': PropertyType(True, is_str()) }
class SamSimpleTable(SamResourceMacro): """SAM simple table macro. """ resource_type = 'AWS::Serverless::SimpleTable' property_types = { 'PrimaryKey': PropertyType(False, dict_of(is_str(), is_str())), 'ProvisionedThroughput': PropertyType(False, dict_of(is_str(), one_of(is_type(int), is_type(dict)))), 'TableName': PropertyType(False, one_of(is_str(), is_type(dict))), 'Tags': PropertyType(False, is_type(dict)), 'SSESpecification': PropertyType(False, is_type(dict)) } attribute_type_conversions = { 'String': 'S', 'Number': 'N', 'Binary': 'B' } def to_cloudformation(self, **kwargs): dynamodb_resources = self._construct_dynamodb_table() return [dynamodb_resources] def _construct_dynamodb_table(self): dynamodb_table = DynamoDBTable(self.logical_id, depends_on=self.depends_on, attributes=self.resource_attributes) if self.PrimaryKey: primary_key = { 'AttributeName': self.PrimaryKey['Name'], 'AttributeType': self._convert_attribute_type(self.PrimaryKey['Type']) } else: primary_key = {'AttributeName': 'id', 'AttributeType': 'S'} dynamodb_table.AttributeDefinitions = [primary_key] dynamodb_table.KeySchema = [{ 'AttributeName': primary_key['AttributeName'], 'KeyType': 'HASH' }] if self.ProvisionedThroughput: dynamodb_table.ProvisionedThroughput = self.ProvisionedThroughput else: dynamodb_table.BillingMode = 'PAY_PER_REQUEST' if self.SSESpecification: dynamodb_table.SSESpecification = self.SSESpecification if self.TableName: dynamodb_table.TableName = self.TableName if bool(self.Tags): dynamodb_table.Tags = get_tag_list(self.Tags) return dynamodb_table def _convert_attribute_type(self, attribute_type): if attribute_type in self.attribute_type_conversions: return self.attribute_type_conversions[attribute_type] raise InvalidResourceException(self.logical_id, 'Invalid \'Type\' "{actual}".'.format(actual=attribute_type))
class ApiGatewayUsagePlanKey(Resource): resource_type = "AWS::ApiGateway::UsagePlanKey" property_types = { "KeyId": PropertyType(True, is_str()), "KeyType": PropertyType(True, is_str()), "UsagePlanId": PropertyType(True, is_str()), }
class SQSQueuePolicy(Resource): resource_type = 'AWS::SQS::QueuePolicy' property_types = { 'PolicyDocument': PropertyType(True, is_type(dict)), 'Queues': PropertyType(True, list_of(str)), } runtime_attrs = {"arn": lambda self: fnGetAtt(self.logical_id, "Arn")}
class ApiGatewayDeployment(Resource): resource_type = 'AWS::ApiGateway::Deployment' property_types = { 'Description': PropertyType(False, is_str()), 'RestApiId': PropertyType(True, is_str()), 'StageDescription': PropertyType(False, is_type(dict)), 'StageName': PropertyType(False, is_str()) } runtime_attrs = { "deployment_id": lambda self: ref(self.logical_id), } def make_auto_deployable(self, stage, swagger=None): """ Sets up the resource such that it will triggers a re-deployment when Swagger changes :param swagger: Dictionary containing the Swagger definition of the API """ if not swagger: return # CloudFormation does NOT redeploy the API unless it has a new deployment resource # that points to latest RestApi resource. Append a hash of Swagger Body location to # redeploy only when the API data changes. First 10 characters of hash is good enough # to prevent redeployment when API has not changed # NOTE: `str(swagger)` is for backwards compatibility. Changing it to a JSON or something will break compat generator = logical_id_generator.LogicalIdGenerator( self.logical_id, str(swagger)) self.logical_id = generator.gen() hash = generator.get_hash(length=40) # Get the full hash self.Description = "RestApi deployment id: {}".format(hash) stage.update_deployment_ref(self.logical_id)
class Route53RecordSetGroup(Resource): resource_type = "AWS::Route53::RecordSetGroup" property_types = { "HostedZoneId": PropertyType(False, is_str()), "HostedZoneName": PropertyType(False, is_str()), "RecordSets": PropertyType(False, is_type(list)), }
class Schedule(PushEventSource): """Scheduled executions for SAM Functions.""" resource_type = 'Schedule' principal = 'events.amazonaws.com' property_types = { 'Schedule': PropertyType(True, is_str()), 'Input': PropertyType(False, is_str()), 'Enabled': PropertyType(False, is_type(bool)), 'Name': PropertyType(False, is_str()), 'Description': PropertyType(False, is_str()) } def to_cloudformation(self, **kwargs): """Returns the CloudWatch Events Rule and Lambda Permission to which this Schedule event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this Schedule event expands :rtype: list """ function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] events_rule = EventsRule(self.logical_id) resources.append(events_rule) events_rule.ScheduleExpression = self.Schedule if self.Enabled is not None: events_rule.State = "ENABLED" if self.Enabled else "DISABLED" events_rule.Name = self.Name events_rule.Description = self.Description events_rule.Targets = [self._construct_target(function)] source_arn = events_rule.get_runtime_attr("arn") if CONDITION in function.resource_attributes: events_rule.set_resource_attribute( CONDITION, function.resource_attributes[CONDITION]) resources.append( self._construct_permission(function, source_arn=source_arn)) return resources def _construct_target(self, function): """Constructs the Target property for the CloudWatch Events Rule. :returns: the Target property :rtype: dict """ target = { 'Arn': function.get_runtime_attr("arn"), 'Id': self.logical_id + 'LambdaTarget' } if self.Input is not None: target['Input'] = self.Input return target
class IoTRule(PushEventSource): resource_type = 'IoTRule' principal = 'iot.amazonaws.com' property_types = { 'Sql': PropertyType(True, is_str()), 'AwsIotSqlVersion': PropertyType(False, is_str()) } def to_cloudformation(self, **kwargs): function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] resource = 'rule/${RuleName}' partition = ArnGenerator.get_partition_name() source_arn = fnSub( ArnGenerator.generate_arn(partition=partition, service='iot', resource=resource), {'RuleName': ref(self.logical_id)}) source_account = fnSub('${AWS::AccountId}') resources.append( self._construct_permission(function, source_arn=source_arn, source_account=source_account)) resources.append(self._construct_iot_rule(function)) return resources def _construct_iot_rule(self, function): rule = IotTopicRule(self.logical_id) payload = { 'Sql': self.Sql, 'RuleDisabled': False, 'Actions': [{ 'Lambda': { 'FunctionArn': function.get_runtime_attr("arn") } }] } if self.AwsIotSqlVersion: payload['AwsIotSqlVersion'] = self.AwsIotSqlVersion rule.TopicRulePayload = payload if CONDITION in function.resource_attributes: rule.set_resource_attribute( CONDITION, function.resource_attributes[CONDITION]) return rule
class ApiGatewayBasePathMapping(Resource): resource_type = "AWS::ApiGateway::BasePathMapping" property_types = { "BasePath": PropertyType(False, is_str()), "DomainName": PropertyType(True, is_str()), "RestApiId": PropertyType(False, is_str()), "Stage": PropertyType(False, is_str()), }
class ApiGatewayDomainName(Resource): resource_type = "AWS::ApiGateway::DomainName" property_types = { "RegionalCertificateArn": PropertyType(False, is_str()), "DomainName": PropertyType(True, is_str()), "EndpointConfiguration": PropertyType(False, is_type(dict)), "CertificateArn": PropertyType(False, is_str()), }
class ApiGatewayDomainName(Resource): resource_type = 'AWS::ApiGateway::DomainName' property_types = { 'RegionalCertificateArn': PropertyType(False, is_str()), 'DomainName': PropertyType(True, is_str()), 'EndpointConfiguration': PropertyType(False, is_type(dict)), 'CertificateArn': PropertyType(False, is_str()) }
class ApiGatewayBasePathMapping(Resource): resource_type = 'AWS::ApiGateway::BasePathMapping' property_types = { 'BasePath': PropertyType(False, is_str()), 'DomainName': PropertyType(True, is_str()), 'RestApiId': PropertyType(False, is_str()), 'Stage': PropertyType(False, is_str()) }
class ApiGatewayV2DomainName(Resource): resource_type = "AWS::ApiGatewayV2::DomainName" property_types = { "DomainName": PropertyType(True, is_str()), "DomainNameConfigurations": PropertyType(False, list_of(is_type(dict))), "Tags": PropertyType(False, is_type(dict)), }
class ApiGatewayV2ApiMapping(Resource): resource_type = "AWS::ApiGatewayV2::ApiMapping" property_types = { "ApiId": PropertyType(True, is_str()), "ApiMappingKey": PropertyType(False, is_str()), "DomainName": PropertyType(True, is_str()), "Stage": PropertyType(True, is_str()), }
class CloudWatchEvent(PushEventSource): """CloudWatch Events event source for SAM Functions.""" resource_type = 'CloudWatchEvent' principal = 'events.amazonaws.com' property_types = { 'EventBusName': PropertyType(False, is_str()), 'Pattern': PropertyType(False, is_type(dict)), 'Input': PropertyType(False, is_str()), 'InputPath': PropertyType(False, is_str()) } def to_cloudformation(self, **kwargs): """Returns the CloudWatch Events Rule and Lambda Permission to which this CloudWatch Events event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this CloudWatch Events event expands :rtype: list """ function = kwargs.get('function') if not function: raise TypeError("Missing required keyword argument: function") resources = [] events_rule = EventsRule(self.logical_id) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern events_rule.Targets = [self._construct_target(function)] if CONDITION in function.resource_attributes: events_rule.set_resource_attribute( CONDITION, function.resource_attributes[CONDITION]) resources.append(events_rule) source_arn = events_rule.get_runtime_attr("arn") resources.append( self._construct_permission(function, source_arn=source_arn)) return resources def _construct_target(self, function): """Constructs the Target property for the CloudWatch Events Rule. :returns: the Target property :rtype: dict """ target = { 'Arn': function.get_runtime_attr("arn"), 'Id': self.logical_id + 'LambdaTarget' } if self.Input is not None: target['Input'] = self.Input if self.InputPath is not None: target['InputPath'] = self.InputPath return target
class SNSSubscription(Resource): resource_type = 'AWS::SNS::Subscription' property_types = { 'Endpoint': PropertyType(True, is_str()), 'Protocol': PropertyType(True, is_str()), 'TopicArn': PropertyType(True, is_str()), 'Region': PropertyType(False, is_str()), 'FilterPolicy': PropertyType(False, is_type(dict)) }
class LambdaEventInvokeConfig(Resource): resource_type = "AWS::Lambda::EventInvokeConfig" property_types = { "DestinationConfig": PropertyType(False, is_type(dict)), "FunctionName": PropertyType(True, is_str()), "MaximumEventAgeInSeconds": PropertyType(False, is_type(int)), "MaximumRetryAttempts": PropertyType(False, is_type(int)), "Qualifier": PropertyType(True, is_str()), }
class Schedule(EventSource): """Scheduled executions for SAM State Machine.""" resource_type = "Schedule" principal = "events.amazonaws.com" property_types = { "Schedule": PropertyType(True, is_str()), "Input": PropertyType(False, is_str()), "Enabled": PropertyType(False, is_type(bool)), "Name": PropertyType(False, is_str()), "Description": PropertyType(False, is_str()), } def to_cloudformation(self, resource, **kwargs): """Returns the EventBridge Rule and IAM Role to which this Schedule event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this Schedule event expands :rtype: list """ resources = [] permissions_boundary = kwargs.get("permissions_boundary") events_rule = EventsRule(self.logical_id) resources.append(events_rule) events_rule.ScheduleExpression = self.Schedule if self.Enabled is not None: events_rule.State = "ENABLED" if self.Enabled else "DISABLED" events_rule.Name = self.Name events_rule.Description = self.Description if CONDITION in resource.resource_attributes: events_rule.set_resource_attribute( CONDITION, resource.resource_attributes[CONDITION]) role = self._construct_role(resource, permissions_boundary) resources.append(role) events_rule.Targets = [self._construct_target(resource, role)] return resources def _construct_target(self, resource, role): """Constructs the Target property for the EventBridge Rule. :returns: the Target property :rtype: dict """ target = { "Arn": resource.get_runtime_attr("arn"), "Id": self.logical_id + "StepFunctionsTarget", "RoleArn": role.get_runtime_attr("arn"), } if self.Input is not None: target["Input"] = self.Input return target
class LambdaPermission(Resource): resource_type = 'AWS::Lambda::Permission' property_types = { 'Action': PropertyType(True, is_str()), 'FunctionName': PropertyType(True, is_str()), 'Principal': PropertyType(True, is_str()), 'SourceAccount': PropertyType(False, is_str()), 'SourceArn': PropertyType(False, is_str()) }
class SNSSubscription(Resource): resource_type = "AWS::SNS::Subscription" property_types = { "Endpoint": PropertyType(True, is_str()), "Protocol": PropertyType(True, is_str()), "TopicArn": PropertyType(True, is_str()), "Region": PropertyType(False, is_str()), "FilterPolicy": PropertyType(False, is_type(dict)), }
class ApiGatewayDeployment(Resource): _X_HASH_DELIMITER = "||" resource_type = "AWS::ApiGateway::Deployment" property_types = { "Description": PropertyType(False, is_str()), "RestApiId": PropertyType(True, is_str()), "StageDescription": PropertyType(False, is_type(dict)), "StageName": PropertyType(False, is_str()), } runtime_attrs = {"deployment_id": lambda self: ref(self.logical_id)} def make_auto_deployable(self, stage, openapi_version=None, swagger=None, domain=None, redeploy_restapi_parameters=None): """ Sets up the resource such that it will trigger a re-deployment when Swagger changes or the openapi version changes or a domain resource changes. :param swagger: Dictionary containing the Swagger definition of the API :param openapi_version: string containing value of OpenApiVersion flag in the template :param domain: Dictionary containing the custom domain configuration for the API :param redeploy_restapi_parameters: Dictionary containing the properties for which rest api will be redeployed """ if not swagger: return # CloudFormation does NOT redeploy the API unless it has a new deployment resource # that points to latest RestApi resource. Append a hash of Swagger Body location to # redeploy only when the API data changes. First 10 characters of hash is good enough # to prevent redeployment when API has not changed # NOTE: `str(swagger)` is for backwards compatibility. Changing it to a JSON or something will break compat hash_input = [str(swagger)] if openapi_version: hash_input.append(str(openapi_version)) if domain: hash_input.append(json.dumps(domain)) if redeploy_restapi_parameters: function_names = redeploy_restapi_parameters.get("function_names") else: function_names = None # The deployment logical id is <api logicalId> + "Deployment" # The keyword "Deployment" is removed and all the function names associated with api is obtained if function_names and function_names.get(self.logical_id[:-10], None): hash_input.append(function_names.get(self.logical_id[:-10], "")) data = self._X_HASH_DELIMITER.join(hash_input) generator = logical_id_generator.LogicalIdGenerator( self.logical_id, data) self.logical_id = generator.gen() digest = generator.get_hash(length=40) # Get the full hash self.Description = "RestApi deployment id: {}".format(digest) stage.update_deployment_ref(self.logical_id)
class CloudWatchEvent(EventSource): """CloudWatch Events/EventBridge event source for SAM State Machine.""" resource_type = "CloudWatchEvent" principal = "events.amazonaws.com" property_types = { "EventBusName": PropertyType(False, is_str()), "Pattern": PropertyType(False, is_type(dict)), "Input": PropertyType(False, is_str()), "InputPath": PropertyType(False, is_str()), } def to_cloudformation(self, resource, **kwargs): """Returns the CloudWatch Events/EventBridge Rule and IAM Role to which this CloudWatch Events/EventBridge event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this CloudWatch Events/EventBridge event expands :rtype: list """ resources = [] permissions_boundary = kwargs.get("permissions_boundary") events_rule = EventsRule(self.logical_id) events_rule.EventBusName = self.EventBusName events_rule.EventPattern = self.Pattern if CONDITION in resource.resource_attributes: events_rule.set_resource_attribute( CONDITION, resource.resource_attributes[CONDITION]) resources.append(events_rule) role = self._construct_role(resource, permissions_boundary) resources.append(role) events_rule.Targets = [self._construct_target(resource, role)] return resources def _construct_target(self, resource, role): """Constructs the Target property for the CloudWatch Events/EventBridge Rule. :returns: the Target property :rtype: dict """ target = { "Arn": resource.get_runtime_attr("arn"), "Id": self.logical_id + "StepFunctionsTarget", "RoleArn": role.get_runtime_attr("arn"), } if self.Input is not None: target["Input"] = self.Input if self.InputPath is not None: target["InputPath"] = self.InputPath return target
class LambdaAlias(Resource): resource_type = 'AWS::Lambda::Alias' property_types = { 'Description': PropertyType(False, is_str()), 'Name': PropertyType(False, is_str()), 'FunctionName': PropertyType(True, one_of(is_str(), is_type(dict))), 'FunctionVersion': PropertyType(True, one_of(is_str(), is_type(dict))) } runtime_attrs = {"arn": lambda self: ref(self.logical_id)}
class LambdaEventSourceMapping(Resource): resource_type = 'AWS::Lambda::EventSourceMapping' property_types = { 'BatchSize': PropertyType(False, is_type(int)), 'Enabled': PropertyType(False, is_type(bool)), 'EventSourceArn': PropertyType(True, is_str()), 'FunctionName': PropertyType(True, is_str()), 'StartingPosition': PropertyType(True, is_str()) } runtime_attrs = {"name": lambda self: ref(self.logical_id)}
class LambdaAlias(Resource): resource_type = "AWS::Lambda::Alias" property_types = { "Description": PropertyType(False, is_str()), "Name": PropertyType(False, is_str()), "FunctionName": PropertyType(True, one_of(is_str(), is_type(dict))), "FunctionVersion": PropertyType(True, one_of(is_str(), is_type(dict))), "ProvisionedConcurrencyConfig": PropertyType(False, is_type(dict)), } runtime_attrs = {"arn": lambda self: ref(self.logical_id)}
class CloudWatchLogs(PushEventSource): """CloudWatch Logs event source for SAM Functions.""" resource_type = "CloudWatchLogs" principal = "logs.amazonaws.com" property_types = { "LogGroupName": PropertyType(True, is_str()), "FilterPattern": PropertyType(True, is_str()) } def to_cloudformation(self, **kwargs): """Returns the CloudWatch Logs Subscription Filter and Lambda Permission to which this CloudWatch Logs event source corresponds. :param dict kwargs: no existing resources need to be modified :returns: a list of vanilla CloudFormation Resources, to which this push event expands :rtype: list """ function = kwargs.get("function") if not function: raise TypeError("Missing required keyword argument: function") source_arn = self.get_source_arn() permission = self._construct_permission(function, source_arn=source_arn) subscription_filter = self.get_subscription_filter( function, permission) resources = [permission, subscription_filter] return resources def get_source_arn(self): resource = "log-group:${__LogGroupName__}:*" partition = ArnGenerator.get_partition_name() return fnSub( ArnGenerator.generate_arn(partition=partition, service="logs", resource=resource), {"__LogGroupName__": self.LogGroupName}, ) def get_subscription_filter(self, function, permission): subscription_filter = SubscriptionFilter( self.logical_id, depends_on=[permission.logical_id]) subscription_filter.LogGroupName = self.LogGroupName subscription_filter.FilterPattern = self.FilterPattern subscription_filter.DestinationArn = function.get_runtime_attr("arn") if "Condition" in function.resource_attributes: subscription_filter.set_resource_attribute( "Condition", function.resource_attributes["Condition"]) return subscription_filter
class LambdaVersion(Resource): resource_type = 'AWS::Lambda::Version' property_types = { 'CodeSha256': PropertyType(False, is_str()), 'Description': PropertyType(False, is_str()), 'FunctionName': PropertyType(True, one_of(is_str(), is_type(dict))) } runtime_attrs = { "arn": lambda self: ref(self.logical_id), "version": lambda self: fnGetAtt(self.logical_id, "Version") }
class SubscriptionFilter(Resource): resource_type = "AWS::Logs::SubscriptionFilter" property_types = { "LogGroupName": PropertyType(True, is_str()), "FilterPattern": PropertyType(True, is_str()), "DestinationArn": PropertyType(True, is_str()), } runtime_attrs = { "name": lambda self: ref(self.logical_id), "arn": lambda self: fnGetAtt(self.logical_id, "Arn") }
class NestedStack(Resource): resource_type = 'AWS::CloudFormation::Stack' # TODO: support passthrough parameters for stacks (Conditions, etc) property_types = { 'TemplateURL': PropertyType(True, is_str()), 'Parameters': PropertyType(False, is_type(dict)), 'NotificationArns': PropertyType(False, list_of(is_str())), 'Tags': PropertyType(False, list_of(is_type(dict))), 'TimeoutInMinutes': PropertyType(False, is_type(int)) } runtime_attrs = {"stack_id": lambda self: ref(self.logical_id)}