Files
civ6-play-by-cloud-turn-not…/serverless.yaml
2021-04-12 20:34:47 +10:00

786 lines
27 KiB
YAML

service: civ6-pbc${{env:SERVERLESS_SERVICE_SUFFIX, ''}}
provider:
name: aws
stackName: ${{self:service}}-${{self:provider.stage}}
runtime: nodejs12.x
memorySize: 128
timeout: 20
logRetentionInDays: ${{self:custom.stageConfig.logRetentionInDays}}
stage: ${{opt:stage, env:NODE_ENV, 'development'}}
profile: ${{self:custom.stageConfig.profile}}
region: us-west-2
# Access serverless variables with ${{self:custom.example}} so it doesn't conflict with AWS ${AWS::Example}
variableSyntax: "\\${{([ ~:a-zA-Z0-9._@\\'\",\\-\\/\\(\\)]+?)}}"
logs:
restApi:
format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod", "resourcePath":"$context.resourcePath", "status":"$context.status", "protocol":"$context.protocol", "responseLength":"$context.responseLength" }'
level: INFO # TODO: add custom field for setting this; default to ERROR for prod
environment:
USER_TABLE: !Ref UserTable
GAME_TABLE: !Ref GameTable
MAIN_TABLE: !Ref MainTable
# Enable connection reuse for AWS SDK for instant performance boost
# https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html
AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1
cors: true
package:
individually: true
plugins:
- serverless-dotenv-plugin
- serverless-domain-manager
- serverless-prune-plugin
- serverless-plugin-tracing
- serverless-iam-roles-per-function
- serverless-webpack
- serverless-apigateway-service-proxy
- serverless-stack-output
- serverless-cloudside-plugin
- serverless-plugin-aws-alerts
- serverless-plugin-metric
- '@brettstack/serverless-amplify-plugin'
- serverless-offline
- serverless-stack-termination-protection
custom:
stages:
development:
profile: civ6-pbc_dev
logRetentionInDays: 1
amplify:
api:
domainEnabled: false
alarms:
notificationEmail: ${{env:ALARMS_NOTIFICATION_EMAIL}}
staging:
profile: civ6-pbc_staging
logRetentionInDays: 3
isDomainRoute53: false
api:
domainEnabled: true
domainName: staging.api.civ.halfstack.software
# validationDomain: halfstack.software
amplify:
domainName: staging.civ.halfstack.software
alarms:
notificationEmail: alerts@halfstack.software
production:
profile: civ6-pbc_prod
logRetentionInDays: 14
# retainDataResources: true
pointInTimeRecoveryEnabled: true
isDomainRoute53: false
api:
domainEnabled: true
domainName: api.civ.halfstack.software
# validationDomain: halfstack.software
amplify:
domainName: civ.halfstack.software
alarms:
notificationEmail: alerts@halfstack.software
stageConfig: ${{self:custom.stages.${{self:provider.stage}}}}
prune:
automatic: true
number: 10
customDomain:
domainName: ${{self:custom.stageConfig.api.domainName, ''}}
certificateName: ${{self:custom.stageConfig.api.domainName, ''}}
# enabled: false
enabled: ${{self:custom.stageConfig.api.domainEnabled, false}}
autoDomain: true #${{self:custom.stageConfig.api.domainEnabled, false}}
createRoute53Record: ${{self:custom.stageConfig.api.isDomainRoute53, false}}
serverless-offline:
httpPort: 4911
noPrependStageInUrl: true
amplify:
isManual: true
domainName: ${{self:custom.stageConfig.amplify.domainName, ''}}
buildSpecValues:
artifactBaseDirectory: packages/ui/build
preBuildWorkingDirectory: packages/ui
buildCommandEnvVars:
prefix: 'REACT_APP_'
allow:
- ApiEndpoint
- CognitoIdentityPoolId
- CognitoUserPoolId
- CognitoUserPoolClientId
# dotenv:
# path: ../../.env
# amplify:
# accessTokenSecretName: AmplifyGithub3
# repository: https://github.com/brettstack/civ6-play-by-cloud-turn-notifier
# branch: ${{self:custom.stageConfig.branch}}
# domainName: ${{self:custom.stageConfig.domainName, ''}}
# buildSpecValues:
# artifactBaseDirectory: packages/ui/build
# preBuildWorkingDirectory: packages/ui
# # TODO: update amplify-plugin to accept custom buildSpecValues.preBuild command so we don't have to set the entire buildSpec
# buildSpec: |-
# version: 0.1
# frontend:
# phases:
# preBuild:
# commands:
# - npm ci
# - cd packages/ui
# - npm ci
# build:
# commands:
# - npm run build
# artifacts:
# baseDirectory: packages/ui/build
# files:
# - '**/*'
# cache:
# paths:
# - node_modules/**/*
webpack:
webpackConfig: ./functions.webpack.config.js
excludeFiles: packages/**/*.test.[t|j]s
output:
file: ./stack-outputs.${{self:provider.stage}}.json
alerts:
dashboards: false
nameTemplate: $[functionName]-$[metricName]-Alarm
topics:
alarm:
topic: ${{self:service}}-${{self:provider.stage}}-alarm
notifications:
- protocol: email
endpoint: ${{self:custom.stageConfig.alarms.notificationEmail}}
# TODO: Add short and long alarms for each
alarms:
# - functionThrottles
- functionErrors
- functionInvocations
- functionDuration
definitions:
functionDuration:
threshold: 2000
evaluationPeriods: 5
datapointsToAlarm: 5
statistic: p95
functionErrors:
threshold: 2
period: 300
metrics:
- name: SKIP_INACTIVE
pattern: '{ $.message = "PROCESS_MESSAGE:SKIP_INACTIVE" }'
functions: [webhook]
namespace: ${{self:service}}
- name: GAME_MARKED_INACTIVE
pattern: '{ $.message = "GAME_CONTROLLER:GAME_MARKED_INACTIVE" }'
functions: [webhook]
namespace: ${{self:service}}
- name: NOTIFICATION_SENT
pattern: '{ $.message = "PROCESS_MESSAGE:NOTIFICATION_SENT" }'
functions: [webhook]
namespace: ${{self:service}}
- name: GAME_CREATED
pattern: '{ $.message = "GAME_CONTROLLER:GAME_CREATED" }'
functions: [express]
namespace: ${{self:service}}
serverlessTerminationProtection:
stages:
- staging
- production
apiGatewayServiceProxies:
- sqs:
path: /webhook
method: post
queueName: !GetAtt WebhookSqsQueue.QueueName
requestParameters:
integration.request.querystring.MessageAttribute.1.Name: "'gameId'"
integration.request.querystring.MessageAttribute.1.Value.StringValue: method.request.querystring.gameId
integration.request.querystring.MessageAttribute.1.Value.DataType: "'String'"
functions:
webhook:
# NOTE: Lambda generally uses 5 pollers at low load and it's recommended to set reservedConcurrency to at least 5
# https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#events-sqs-scaling
# https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#events-sqs-eventsource
reservedConcurrency: 5
memorySize: 512
handler: packages/api/functions/webhook/lambda.webhookHandlerMiddy
layers:
- !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:12"
events:
- sqs:
arn: !GetAtt WebhookSqsQueue.Arn
batchSize: 100
maximumBatchingWindow: 20
iamRoleStatements:
- Effect: Allow
Action:
- sqs:SendMessage
Resource: !GetAtt WebhookSqsQueueDlq.Arn
- Effect: "Allow"
Action:
- dynamodb:GetItem
- dynamodb:UpdateItem
Resource:
- !GetAtt GameTable.Arn
- !GetAtt UserTable.Arn
express:
handler: packages/api/functions/express/lambda.handler
memorySize: 1024
layers:
- !Sub "arn:aws:lambda:${AWS::Region}:580247275435:layer:LambdaInsightsExtension:12"
events:
- http:
method: ANY
path: /
authorizer:
type: COGNITO_USER_POOLS
authorizerId: !Ref ApiGatewayAuthorizer
- http:
method: ANY
path: '{proxy+}'
authorizer:
type: COGNITO_USER_POOLS
authorizerId: !Ref ApiGatewayAuthorizer
iamRoleStatements:
- Effect: "Allow"
Action:
- "xray:PutTraceSegments"
- "xray:PutTelemetryRecords"
Resource:
- "*"
- Effect: "Allow"
Action:
- dynamodb:BatchGetItem
- dynamodb:BatchWriteItem
- dynamodb:DeleteItem
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:Query
- dynamodb:Scan
- dynamodb:UpdateItem
Resource:
- !GetAtt GameTable.Arn
- !GetAtt UserTable.Arn
autoConfirmUser:
handler: packages/api/functions/cognito/auto-confirm-user.handler
postAuthN:
handler: packages/api/functions/cognito/post-authentication.handler
iamRoleStatements:
- Effect: "Allow"
Action:
- dynamodb:GetItem
- dynamodb:PutItem
Resource:
- !GetAtt UserTable.Arn
resources:
Conditions:
IsApiCustomDomainEnabled:
!Equals
- ${{self:custom.stageConfig.api.domainEnabled, false}}
- true
# RetainDataResources:
# !Equals
# - ${{self:custom.stageConfig.retainDataResources, false}}
# - true
Resources:
WebhookSqsQueue:
Type: AWS::SQS::Queue
Properties:
# NOTE: Following guidance here to reduce the chance of Lambda throttling
# https://medium.com/@zaccharles/lambda-concurrency-limits-and-sqs-triggers-dont-mix-well-sometimes-eb23d90122e0
# https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#events-sqs-queueconfig
# https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#events-sqs-eventsource
# functions.webhook.timeout * 6 + functions.webhook.events.sqs.maximumBatchingWindow
# 20 * 6 + 20
VisibilityTimeout: 140
RedrivePolicy:
deadLetterTargetArn: !GetAtt WebhookSqsQueueDlq.Arn
maxReceiveCount: 5
WebhookSqsQueueDlq:
Type: AWS::SQS::Queue
ApiGatewayMethodWebhookPost:
Type: AWS::ApiGateway::Method
Properties:
RequestParameters:
method.request.querystring.gameId: true
Integration:
IntegrationResponses:
- StatusCode: 200
ResponseTemplates:
application/json: '{}'
# NOTE: This only works when deploying to us-east-1 since ACM certs for EDGE APIs
# must exist in us-east-1 due to being associated with a CloudFront Distribution.
# Manually create cert in us-east-1 instead.
# AcmCertificate:
# Type: AWS::CertificateManager::Certificate
# Condition: IsApiCustomDomainEnabled
# Properties:
# DomainName: ${{self:custom.stageConfig.api.domainName}}
# DomainValidationOptions:
# - DomainName: ${{self:custom.stageConfig.api.domainName}}
# ValidationDomain: ${{self:custom.stageConfig.api.validationDomain, ''}}
MainTable:
Type: AWS::DynamoDB::Table
# DeletionPolicy: !If [RetainDataResources, 'Retain', 'Delete']
Properties:
BillingMode: PAY_PER_REQUEST
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: ${{self:custom.stageConfig.pointInTimeRecoveryEnabled, false}}
KeySchema:
- KeyType: HASH
AttributeName: pk
- KeyType: RANGE
AttributeName: sk
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
- AttributeName: data
AttributeType: S
GlobalSecondaryIndexes:
- IndexName: GSI1
KeySchema:
- AttributeName: sk
KeyType: HASH
- AttributeName: data
KeyType: RANGE
Projection:
ProjectionType: ALL
GameTable:
Type: AWS::DynamoDB::Table
# DeletionPolicy: !If [RetainDataResources, 'Retain', 'Delete']
Properties:
BillingMode: PAY_PER_REQUEST
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: ${{self:custom.stageConfig.pointInTimeRecoveryEnabled, false}}
KeySchema:
- KeyType: HASH
AttributeName: id
AttributeDefinitions:
- AttributeName: id
AttributeType: S
UserTable:
Type: AWS::DynamoDB::Table
# DeletionPolicy: !If [RetainDataResources, 'Retain', 'Delete']
Properties:
BillingMode: PAY_PER_REQUEST
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: false
KeySchema:
- KeyType: HASH
AttributeName: id
AttributeDefinitions:
- AttributeName: id
AttributeType: S
CognitoUserPool:
Type: AWS::Cognito::UserPool
# DeletionPolicy: !If [RetainDataResources, 'Retain', 'Delete']
Properties:
Policies:
PasswordPolicy:
MinimumLength: 6
Schema:
- AttributeDataType: String
Name: email
Required: true
AutoVerifiedAttributes:
- email
# EmailConfiguration:
# EmailSendingAccount: DEVELOPER
# ReplyToEmailAddress: no-reply@halfstack.software
# SourceArn: arn:aws:ses:us-west-2:xxxx:identity/no-reply@halfstack.software
LambdaConfig:
PreSignUp: !GetAtt AutoConfirmUserLambdaFunction.Arn
PostAuthentication: !GetAtt PostAuthNLambdaFunction.Arn
CognitoUserPoolClient:
Type: AWS::Cognito::UserPoolClient
# DeletionPolicy: !If [RetainDataResources, 'Retain', 'Delete']
Properties:
UserPoolId: !Ref CognitoUserPool
ClientName: CognitoIdentityPool
GenerateSecret: false
RefreshTokenValidity: 30
CognitoIdentityPool:
Type: AWS::Cognito::IdentityPool
# DeletionPolicy: !If [RetainDataResources, 'Retain', 'Delete']
Properties:
AllowUnauthenticatedIdentities: false
# SupportedLoginProviders:
# graph.facebook.com: 'xxxxx'
# accounts.google.com: 'xxxxx-v02jjpd5r9ig0pdacbhpill2asuqtvnf.apps.googleusercontent.com'
# api.twitter.com:
CognitoIdentityProviders:
- ClientId: !Ref CognitoUserPoolClient
ProviderName: !GetAtt CognitoUserPool.ProviderName
# Allow Cognito to invoke the cognitoAutoConfirm and cognitoPostAuthN functions
AutoConfirmUserLambdaCognitoPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt AutoConfirmUserLambdaFunction.Arn
Principal: cognito-idp.amazonaws.com
SourceArn: !GetAtt CognitoUserPool.Arn
PostAuthNLambdaCognitoPermission:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt PostAuthNLambdaFunction.Arn
Principal: cognito-idp.amazonaws.com
SourceArn: !GetAtt CognitoUserPool.Arn
CognitoUserRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
# Allow authenticated users to assume this role
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
'cognito-identity.amazonaws.com:aud': !Ref CognitoIdentityPool
'ForAnyValue:StringLike':
'cognito-identity.amazonaws.com:amr': authenticated
# Authenticated users are allowed to invoke the API
Policies:
- PolicyName: InvokeApi
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- execute-api:Invoke
Resource: !Sub 'arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ApiGatewayRestApi}/${{self:provider.stage}}/*/*'
Path: '/'
CognitoIdentityPoolRoles:
Type: AWS::Cognito::IdentityPoolRoleAttachment
# DeletionPolicy: !If [RetainDataResources, 'Retain', 'Delete']
Properties:
IdentityPoolId: !Ref CognitoIdentityPool
Roles:
authenticated: !GetAtt CognitoUserRole.Arn
# Due to a Serverless Framework bug, we need to create our own Authorizer, instead of
# simply specifying `authorizer.arn: !GetAtt CognitoUserPool.Arn` in the function.
# https://github.com/serverless/serverless/issues/3212#issuecomment-450574093
ApiGatewayAuthorizer:
DependsOn:
- ApiGatewayRestApi
Type: AWS::ApiGateway::Authorizer
Properties:
Name: CognitoAuthorizer
IdentitySource: method.request.header.Authorization
RestApiId:
Ref: ApiGatewayRestApi
Type: COGNITO_USER_POOLS
ProviderARNs:
- !GetAtt CognitoUserPool.Arn
WebhookIamRoleLambdaExecution:
Properties:
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy'
ExpressIamRoleLambdaExecution:
Properties:
ManagedPolicyArns:
- 'arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy'
MonthlyBudget:
Type: AWS::Budgets::Budget
Properties:
Budget:
BudgetName: Monthly
BudgetType: COST
TimeUnit: MONTHLY
BudgetLimit:
Amount: 5
Unit: USD
NotificationsWithSubscribers:
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 10 # 1st alert sent when % of Monthly total is spent.
Subscribers:
- SubscriptionType: EMAIL
Address: ${{self:custom.stageConfig.alarms.notificationEmail}}
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 95 # 2nd alert sent when % of Monthly total is spent.
Subscribers:
- SubscriptionType: EMAIL
Address: ${{self:custom.stageConfig.alarms.notificationEmail}}
NoNotificationsSentAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
Namespace: civ6-pbc
MetricName: webhook-NOTIFICATION_SENT
AlarmDescription: No notifications have been sent in the past hour. There may be
something wrong with the Webhook service, or there may simply have been no turns
taken. Review the Webhook Lambda Function's CloudWatch Logs and Metrics.
Threshold: 1
Period: 3600
EvaluationPeriods: 1
ComparisonOperator: LessThanThreshold
AlarmActions:
- !Ref AwsAlertsAlarm
- !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:opsitem:2#CATEGORY=Availability
TreatMissingData: missing
Statistic: Sum
NoGamesCreatedAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
Namespace: civ6-pbc
MetricName: express-GAME_CREATED
AlarmDescription: No games have been created in the past day. There may be
something wrong with the game create flow, or there may simply have been no games
created. Review the Express Lambda Function's CloudWatch Logs and Metrics.
Threshold: 1
Period: 86400
EvaluationPeriods: 1
ComparisonOperator: LessThanThreshold
AlarmActions:
- !Ref AwsAlertsAlarm
- !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:opsitem:2#CATEGORY=Availability
TreatMissingData: missing
Statistic: Sum
Dashboard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardBody: !Sub |
{
"start": "-P10D",
"end": "P0D",
"widgets": [
{
"type": "metric",
"x": 0,
"y": 9,
"width": 6,
"height": 6,
"properties": {
"metrics": [
[ "civ6-pbc", "webhook-GAME_MARKED_INACTIVE", { "label": "Games marked inactive" } ],
[ ".", "webhook-SKIP_INACTIVE", { "label": "Inactive games skipped" } ],
[ "AWS/Lambda", "Errors", "FunctionName", "${WebhookLambdaFunction}", "Resource", "${WebhookLambdaFunction}" ]
],
"view": "timeSeries",
"stacked": false,
"region": "${AWS::Region}",
"stat": "Sum",
"period": 3600,
"title": "Webhook"
}
},
{
"type": "metric",
"x": 6,
"y": 9,
"width": 6,
"height": 6,
"properties": {
"annotations": {
"alarms": [
"${WebhookFunctionDurationAlarm.Arn}"
]
},
"yAxis": {
"left": {
"min": 0
}
},
"view": "timeSeries",
"stacked": false,
"region": "${AWS::Region}",
"stat": "p90",
"title": "Duration"
}
},
{
"type": "metric",
"x": 12,
"y": 9,
"width": 3,
"height": 6,
"properties": {
"metrics": [
[ "AWS/SQS", "ApproximateNumberOfMessagesVisible", "QueueName", "${WebhookSqsQueueDlq.QueueName}", { "label": "Number of messages" } ]
],
"view": "timeSeries",
"stacked": false,
"region": "${AWS::Region}",
"stat": "Maximum",
"period": 3600,
"title": "Webhook DLQ"
}
},
{
"type": "alarm",
"x": 18,
"y": 9,
"width": 6,
"height": 3,
"properties": {
"title": "Alarms",
"region": "${AWS::Region}",
"alarms": [
"${WebhookFunctionErrorsAlarm.Arn}",
"${WebhookFunctionDurationAlarm.Arn}",
"${ExpressFunctionErrorsAlarm.Arn}",
"${ExpressFunctionDurationAlarm.Arn}"
]
}
},
{
"type": "metric",
"x": 0,
"y": 0,
"width": 7,
"height": 6,
"properties": {
"annotations": {
"alarms": [
"${NoNotificationsSentAlarm.Arn}"
]
},
"yAxis": {
"left": {
"min": 0
}
},
"view": "timeSeries",
"stacked": false,
"region": "${AWS::Region}",
"stat": "Sum",
"title": "Notifications sent"
}
},
{
"type": "metric",
"x": 7,
"y": 0,
"width": 3,
"height": 6,
"properties": {
"annotations": {
"alarms": [
"${NoGamesCreatedAlarm.Arn}"
]
},
"yAxis": {
"left": {
"min": 0
}
},
"view": "timeSeries",
"stacked": false,
"region": "${AWS::Region}",
"stat": "Sum",
"title": "Games created"
}
},
{
"type": "metric",
"x": 15,
"y": 0,
"width": 9,
"height": 3,
"properties": {
"metrics": [
[ "civ6-pbc", "webhook-NOTIFICATION_SENT", { "label": "Today", "period": 86400 } ],
[ "...", { "label": "This week", "period": 604800 } ],
[ "...", { "label": "This month", "yAxis": "right", "period": 2678400 } ]
],
"view": "singleValue",
"stacked": false,
"region": "${AWS::Region}",
"stat": "Sum",
"title": "Notifications sent",
"start": "-P4W",
"end": "P0D",
"period": 2678400
}
},
{
"type": "log",
"x": 15,
"y": 3,
"width": 3,
"height": 3,
"properties": {
"query": "SOURCE '/aws/lambda/civ6-pbc-production-webhook' | FIELDS gameId\n| STATS count_distinct(gameId) as activeGames",
"region": "${AWS::Region}",
"title": "Active games",
"view": "singleValue"
}
},
{
"type": "log",
"x": 0,
"y": 12,
"width": 24,
"height": 6,
"properties": {
"query": "SOURCE '/aws/lambda/${WebhookLambdaFunction}' | SOURCE '/aws/lambda/${ExpressLambdaFunction}' | FIELDS @timestamp, @message\n| SORT @timestamp desc\n| FILTER @message LIKE /Invoke Error/ OR errorType = 'Error' OR level = 'error'\n| DISPLAY @ingestionTime, errorMessage, message",
"region": "${AWS::Region}",
"stacked": false,
"view": "table"
}
}
]
}
Outputs:
CognitoUserPoolId:
Description: ID of the Cognito User Pool
Value: !Ref CognitoUserPool
CognitoUserPoolClientId:
Description: 'Client ID of the Cognito User Pool App: Identity Pool'
Value: !Ref CognitoUserPoolClient
CognitoIdentityPoolId:
Description: ID of the Cognito Identity Pool
Value: !Ref CognitoIdentityPool
UserTableName:
Value: !Ref UserTable
GameTableName:
Value: !Ref GameTable
ApiEndpoint:
Value:
Fn::If:
- IsApiCustomDomainEnabled
- https://${{self:custom.stageConfig.api.domainName, ''}}
- !Sub https://${ApiGatewayRestApi}.execute-api.${AWS::Region}.amazonaws.com/${{self:provider.stage}}