AWSTemplateFormatVersion: "2010-09-09" Outputs: AccessKey: Description: "DO user access key" Value: !Ref DOUserKey AccessSecret: Description: "DO user secret key" Value: !GetAtt DOUserKey.SecretAccessKey Resources: # DynamoDB Feeds table FeedsTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub "${AWS::StackName}_Feeds" BillingMode: "PROVISIONED" AttributeDefinitions: - AttributeName: "HashID" AttributeType: "S" - AttributeName: "UserID" AttributeType: "S" - AttributeName: "CreatedAt" AttributeType: "N" KeySchema: - AttributeName: "HashID" KeyType: "HASH" GlobalSecondaryIndexes: - IndexName: "UserID-HashID-Index" KeySchema: - AttributeName: "UserID" KeyType: "HASH" - AttributeName: "CreatedAt" KeyType: "RANGE" Projection: ProjectionType: "KEYS_ONLY" ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 ProvisionedThroughput: ReadCapacityUnits: 10 WriteCapacityUnits: 5 TimeToLiveSpecification: AttributeName: "ExpirationTime" Enabled: true # DynamoDB Pledges tables PledgesTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub "${AWS::StackName}_Pledges" BillingMode: "PROVISIONED" AttributeDefinitions: - AttributeName: "PatronID" AttributeType: "N" KeySchema: - AttributeName: "PatronID" KeyType: "HASH" ProvisionedThroughput: ReadCapacityUnits: 1 WriteCapacityUnits: 1 # Feeds table read/write scaling targets FeedsTableReadScaling: Type: AWS::ApplicationAutoScaling::ScalableTarget Properties: MaxCapacity: 50 MinCapacity: 5 ResourceId: !Sub - "table/${TableName}" - { TableName: !Ref FeedsTable } RoleARN: !GetAtt DynamoScalingRole.Arn ScalableDimension: dynamodb:table:ReadCapacityUnits ServiceNamespace: dynamodb FeedsTableWriteScaling: Type: AWS::ApplicationAutoScaling::ScalableTarget Properties: MaxCapacity: 50 MinCapacity: 5 ResourceId: !Sub - "table/${TableName}" - { TableName: !Ref FeedsTable } RoleARN: !GetAtt DynamoScalingRole.Arn ScalableDimension: dynamodb:table:WriteCapacityUnits ServiceNamespace: dynamodb # Feeds table read/write scaling policies # https://aws.amazon.com/blogs/database/how-to-use-aws-cloudformation-to-configure-auto-scaling-for-amazon-dynamodb-tables-and-indexes/ # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationautoscaling-scalingpolicy.html FeedsTableWriteScalingPolicy: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Sub "${AWS::StackName}_FeedsTableWriteScalingPolicy" PolicyType: TargetTrackingScaling ScalingTargetId: !Ref FeedsTableWriteScaling TargetTrackingScalingPolicyConfiguration: TargetValue: 70 ScaleInCooldown: 60 ScaleOutCooldown: 60 PredefinedMetricSpecification: PredefinedMetricType: DynamoDBWriteCapacityUtilization FeedsTableReadScalingPolicy: Type: AWS::ApplicationAutoScaling::ScalingPolicy Properties: PolicyName: !Sub "${AWS::StackName}_FeedsTableReadScalingPolicy" PolicyType: TargetTrackingScaling ScalingTargetId: !Ref FeedsTableReadScaling TargetTrackingScalingPolicyConfiguration: TargetValue: 70 ScaleInCooldown: 60 ScaleOutCooldown: 60 PredefinedMetricSpecification: PredefinedMetricType: DynamoDBReadCapacityUtilization # Common scaling role for DynamoDB DynamoScalingRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${AWS::StackName}_DynamoDBScalingRole" AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - application-autoscaling.amazonaws.com Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: "root" PolicyDocument: Version: "2012-10-17" Statement: Effect: Allow Action: - "dynamodb:DescribeTable" - "dynamodb:UpdateTable" - "cloudwatch:PutMetricAlarm" - "cloudwatch:DescribeAlarms" - "cloudwatch:GetMetricStatistics" - "cloudwatch:SetAlarmState" - "cloudwatch:DeleteAlarms" Resource: - "*" # CloudWatch log groups LogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Ref "AWS::StackName" RetentionInDays: 30 BackendLogStream: Type: AWS::Logs::LogStream Properties: LogGroupName: !Ref LogGroup LogStreamName: "Backend" # CloudWatch metric filters GetFeedMetricFilter: Type: AWS::Logs::MetricFilter Properties: LogGroupName: !Ref LogGroup FilterPattern: '{ $.event = "get_feed" && $.error NOT EXISTS }' MetricTransformations: - MetricName: "Feed requests" MetricNamespace: !Sub "${AWS::StackName}_Podsync" MetricValue: 1 GetFeedErrorMetricFilter: Type: AWS::Logs::MetricFilter Properties: LogGroupName: !Ref LogGroup FilterPattern: '{ $.event = "get_feed" && $.error != "" }' MetricTransformations: - MetricName: "Feed errors" MetricNamespace: !Sub "${AWS::StackName}_Podsync" MetricValue: 1 GetFeedMetadataMetricFilter: Type: AWS::Logs::MetricFilter Properties: LogGroupName: !Ref LogGroup FilterPattern: '{ $.event = "get_metadata" && $.error NOT EXISTS }' MetricTransformations: - MetricName: "Metadata requests" MetricNamespace: !Sub "${AWS::StackName}_Podsync" MetricValue: 1 CreateFeedMetricFilter: Type: AWS::Logs::MetricFilter Properties: LogGroupName: !Ref LogGroup FilterPattern: '{ $.event = "create_feed" && $.error NOT EXISTS }' MetricTransformations: - MetricName: "Create feed" MetricNamespace: !Sub "${AWS::StackName}_Podsync" MetricValue: 1 CreateFeedErrorMetricFilter: Type: AWS::Logs::MetricFilter Properties: LogGroupName: !Ref LogGroup FilterPattern: '{ $.event = "create_feed" && $.error != "" }' MetricTransformations: - MetricName: "Create feed error" MetricNamespace: !Sub "${AWS::StackName}_Podsync" MetricValue: 1 # Access from DigitalOcean VM DOUser: Type: AWS::IAM::User DependsOn: - FeedsTable - PledgesTable Properties: Policies: - PolicyName: "DynamoDB" PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "dynamodb:GetItem" - "dynamodb:Query" - "dynamodb:PutItem" - "dynamodb:UpdateItem" Resource: - !GetAtt FeedsTable.Arn - !GetAtt PledgesTable.Arn - Effect: Allow Action: - "dynamodb:ListTables" Resource: - "*" - PolicyName: "CloudWatch" PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "logs:CreateLogStream" - "logs:PutLogEvents" Resource: - "*" DOUserKey: Type: AWS::IAM::AccessKey DependsOn: DOUser Properties: UserName: !Ref DOUser