stateMachine/cfn/cfn.yaml (494 lines of code) (raw):
Description: Price-migration orchestration engine.
Parameters:
Stage:
Type: String
AllowedValues:
- PROD
- CODE
- DEV
Default: DEV
ResourceNamePrefix:
Type: String
Default: price-migration-engine
Conditions:
IsProd: !Equals [!Ref "Stage", "PROD"]
Resources:
CohortStateMachineLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/states/${ResourceNamePrefix}-cohort-steps-${Stage}
RetentionInDays: 180
CohortStateMachineExecutionRole:
Type: AWS::IAM::Role
DependsOn:
- CohortStateMachineLogGroup
Properties:
AssumeRolePolicyDocument:
Statement:
Effect: Allow
Principal:
Service: !Sub states.${AWS::Region}.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: LambdaInvokePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-CreatingCohortTableLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-ImportingLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-EstimatingLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-CreatingSalesforceRecordsLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-NotifyingSubscribersLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-UpdatingSalesforceWithNotificationsLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-AmendingLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-UpdatingSalesforceWithAmendsLambda
- Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-ExportingCohortTableToDatalakeLambda
- PolicyName: LoggingPolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- logs:ListLogDeliveries
- logs:GetLogDelivery
- logs:CreateLogDelivery
- logs:UpdateLogDelivery
- logs:DescribeLogGroups
Resource:
- "*"
PriceMigrationLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: /
Policies:
- PolicyName: LambdaPolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- lambda:InvokeFunction
Resource:
- !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/price-migration-lambda-${Stage}:log-stream:*"
- PolicyName: CohortSpecTablePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- dynamodb:Scan
Resource:
- !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/price-migration-engine-cohort-spec-${Stage}"
- !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/price-migration-engine-cohort-spec-${Stage}/*"
- PolicyName: CohortStateMachinePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
- states:StartExecution
Resource:
- !Ref CohortStateMachine
PriceMigrationLambda:
Type: AWS::Lambda::Function
DependsOn:
- PriceMigrationLambdaRole
- CohortStateMachine
Properties:
Description: Kicks off state machines to process each cohort.
FunctionName:
!Sub price-migration-lambda-${Stage}
Code:
S3Bucket: membership-dist
S3Key: !Sub membership/${Stage}/price-migration-engine-lambda/price-migration-engine-lambda.jar
Handler: pricemigrationengine.handlers.MigrationHandler::handleRequest
Environment:
Variables:
stage: !Ref Stage
cohortStateMachineArn: !Ref CohortStateMachine
Role:
Fn::GetAtt:
- PriceMigrationLambdaRole
- Arn
MemorySize: 1536
Runtime: java11
Timeout: 900
PriceMigrationLambdaScheduleRule:
Type: AWS::Events::Rule
DependsOn:
- PriceMigrationLambda
Condition: IsProd
Properties:
Description: Trigger daily 7 am (UTC) kick-off of state machines for active cohorts.
ScheduleExpression: "cron(0 7 ? * * *)"
State: ENABLED
Targets:
- Arn: !GetAtt PriceMigrationLambda.Arn
Id: !Ref PriceMigrationLambda
Input: "null"
PriceMigrationLambdaInvokePermission:
Type: AWS::Lambda::Permission
DependsOn:
- PriceMigrationLambda
- PriceMigrationLambdaScheduleRule
Condition: IsProd
Properties:
Action: lambda:invokeFunction
FunctionName: !Ref PriceMigrationLambda
Principal: events.amazonaws.com
SourceArn: !GetAtt PriceMigrationLambdaScheduleRule.Arn
CohortStateMachine:
Type: AWS::StepFunctions::StateMachine
DependsOn:
- CohortStateMachineLogGroup
- CohortStateMachineExecutionRole
Properties:
StateMachineName:
!Sub ${ResourceNamePrefix}-cohort-steps-${Stage}
RoleArn: !GetAtt CohortStateMachineExecutionRole.Arn
LoggingConfiguration:
Level: ALL
IncludeExecutionData: TRUE
Destinations:
- CloudWatchLogsLogGroup:
LogGroupArn: !GetAtt CohortStateMachineLogGroup.Arn
DefinitionString:
!Sub
- |
{
"Comment": "Price-migration orchestration engine.",
"StartAt": "CreatingCohortTable",
"States": {
"CreatingCohortTable": {
"Type": "Task",
"Comment": "Creating cohort table if it doesn't already exist.",
"Resource": "${CreatingCohortTableLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "Importing",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"Importing": {
"Type": "Task",
"Comment": "Importing subs into cohort table.",
"Resource": "${ImportingLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "Estimating",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"Estimating": {
"Type": "Task",
"Comment": "Calculating start date and new price for each sub in this cohort.",
"Resource": "${EstimatingLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "IsEstimatingComplete",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"IsEstimatingComplete": {
"Type": "Choice",
"Comment": "Is the estimating step complete?",
"Choices": [
{
"Variable": "$.result.isComplete",
"BooleanEquals": false,
"Next": "Estimating"
}
],
"Default": "CreatingSalesforceRecords"
},
"CreatingSalesforceRecords": {
"Type": "Task",
"Comment": "Inserting a price-rise record for each sub in this cohort into Salesforce.",
"Resource": "${CreatingSalesforceRecordsLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "IsCreatingSalesforceRecordsComplete",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"IsCreatingSalesforceRecordsComplete": {
"Type": "Choice",
"Comment": "Is the creating Salesforce records step complete?",
"Choices": [
{
"Variable": "$.result.isComplete",
"BooleanEquals": false,
"Next": "CreatingSalesforceRecords"
}
],
"Default": "NotifyingSubscribers"
},
"NotifyingSubscribers": {
"Type": "Task",
"Comment": "Notifying subscribers via Braze about their new price.",
"Resource": "${NotifyingSubscribersLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "IsNotifyingSubscribersComplete",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"IsNotifyingSubscribersComplete": {
"Type": "Choice",
"Comment": "Is the notification step complete?",
"Choices": [
{
"Variable": "$.result.isComplete",
"BooleanEquals": false,
"Next": "NotifyingSubscribers"
}
],
"Default": "UpdatingSalesforceWithNotifications"
},
"UpdatingSalesforceWithNotifications": {
"Type": "Task",
"Comment": "Updating price-rise records with date that notification was put on queue to Braze.",
"Resource": "${UpdatingSalesforceWithNotificationsLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "Amending",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"Amending": {
"Type": "Task",
"Comment": "Applying price-rise amendment in Zuora on each sub in this cohort.",
"Resource": "${AmendingLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "IsAmendingComplete",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException",
"Lambda.Unknown"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"IsAmendingComplete": {
"Type": "Choice",
"Comment": "Is the amending step complete?",
"Choices": [
{
"Variable": "$.result.isComplete",
"BooleanEquals": false,
"Next": "Amending"
}
],
"Default": "UpdatingSalesforceWithAmendments"
},
"UpdatingSalesforceWithAmendments": {
"Type": "Task",
"Comment": "Updating price-rise record for each sub in this cohort with amendment evidence.",
"Resource": "${UpdatingSalesforceWithAmendsLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "IsUpdatingSalesforceWithAmendmentsComplete",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"IsUpdatingSalesforceWithAmendmentsComplete": {
"Type": "Choice",
"Comment": "Is the updating Salesforce with amendments step complete?",
"Choices": [
{
"Variable": "$.result.isComplete",
"BooleanEquals": false,
"Next": "UpdatingSalesforceWithAmendments"
}
],
"Default": "ExportingCohortTableToDatalake"
},
"ExportingCohortTableToDatalake": {
"Type": "Task",
"Comment": "Exporting cohort data to the datalake.",
"Resource": "${ExportingCohortTableToDatalakeLambdaArn}",
"InputPath": "$.cohortSpec",
"ResultPath": "$.result",
"Next": "IsExportingCohortTableToDatalakeComplete",
"Retry": [
{
"ErrorEquals": [
"Lambda.ServiceException",
"Lambda.AWSLambdaException",
"Lambda.SdkClientException"
],
"IntervalSeconds": 2,
"MaxAttempts": 6,
"BackoffRate": 2
}
]
},
"IsExportingCohortTableToDatalakeComplete": {
"Type": "Choice",
"Comment": "Is the exporting cohort data to the datalake step complete?",
"Choices": [
{
"Variable": "$.result.isComplete",
"BooleanEquals": false,
"Next": "ExportingCohortTableToDatalake"
}
],
"Default": "Complete"
},
"Complete": {
"Type": "Succeed",
"Comment": "All steps are complete."
}
}
}
- CreatingCohortTableLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-CreatingCohortTableLambda
ImportingLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-ImportingLambda
EstimatingLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-EstimatingLambda
CreatingSalesforceRecordsLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-CreatingSalesforceRecordsLambda
NotifyingSubscribersLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-NotifyingSubscribersLambda
UpdatingSalesforceWithNotificationsLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-UpdatingSalesforceWithNotificationsLambda
AmendingLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-AmendingLambda
UpdatingSalesforceWithAmendsLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-UpdatingSalesforceWithAmendsLambda
ExportingCohortTableToDatalakeLambdaArn:
Fn::ImportValue: !Sub membership-${Stage}-${ResourceNamePrefix}-lambda-ExportingCohortTableToDatalakeLambda
PriceMigrationLambdaAlarm:
Type: AWS::CloudWatch::Alarm
DependsOn:
- PriceMigrationLambda
Condition: IsProd
Properties:
AlarmName: "URGENT 9-5 - PROD: Price-rise engine: Failure to kick-off process"
AlarmDescription: >
IMPACT: If this is not dealt with, no price-rise processes will run.
For troubleshooting tips, see https://github.com/guardian/price-migration-engine/blob/main/docs/troubleshooting.md
Namespace: AWS/Lambda
MetricName: Errors
Dimensions:
- Name: FunctionName
Value: !Ref PriceMigrationLambda
ComparisonOperator: GreaterThanThreshold
Threshold: 0
Period: 60
EvaluationPeriods: 1
Statistic: Sum
TreatMissingData: ignore
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD
CohortStateMachineAlarm:
Type: AWS::CloudWatch::Alarm
DependsOn:
- CohortStateMachine
Condition: IsProd
Properties:
AlarmName: "URGENT 9-5 - PROD: Price-rise engine: Failure to complete process"
AlarmDescription: >
IMPACT: If this is not dealt with, the price rise may be unable to continue.
For troubleshooting tips, see https://github.com/guardian/price-migration-engine/blob/main/docs/troubleshooting.md
Namespace: AWS/States
MetricName: ExecutionsFailed
Dimensions:
- Name: StateMachineArn
Value: !Ref CohortStateMachine
ComparisonOperator: GreaterThanThreshold
Threshold: 0
Period: 60
EvaluationPeriods: 1
Statistic: Sum
TreatMissingData: ignore
AlarmActions:
- !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:alarms-handler-topic-PROD