{
  "Comment": "Orchestrates the RDS refresh process",
  "StartAt": "GetSourceDbInfo",
  "States": {
    "GetSourceDbInfo": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_get_db_info_name}",
        "Payload": {
          "account_id.$": "$.source_account_id",
          "db_name.$": "$.source_db_name",
          "role": "${source_account_role}"
        }
      },
      "TimeoutSeconds": 60,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 5,
          "BackoffRate": 3
        }
      ],
      "ResultPath": "$.source_db_info",
      "Next": "GetTargetDbInfo"
    },
    "GetTargetDbInfo": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_get_db_info_name}",
        "Payload": {
          "account_id.$": "$.target_account_id",
          "db_name.$": "$.target_db_name",
          "role.$": "$.target_account_role"
        }
      },
      "TimeoutSeconds": 60,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 5,
          "BackoffRate": 3
        }
      ],
      "ResultPath": "$.target_db_info",
      "Next": "ValidateConfig"
    },
    "ValidateConfig": {
      "Type": "Choice",
      "Choices": [
        {
          "Not": {
            "Variable": "$.source_db_info.Payload.db_type",
            "StringEqualsPath": "$.target_db_info.Payload.db_type"
          },
          "Next": "ValidationErrorDifferentDbTypes"
        }
      ],
      "Default": "RestoreSnapshotIdCheck"
    },
    "ValidationErrorDifferentDbTypes": {
      "Type": "Fail",
      "CausePath": "States.Format('Databases are of different types. Source database type is {} and target database type is {}', $.source_db_info.Payload.db_type, $.target_db_info.Payload.db_type)"
    },
    "RestoreSnapshotIdCheck": {
      "Comment": "If a snapshot id has been supplied, use it instead of creating a fresh snapshot. The snapshot is prepared (shared with, and where necessary copied into, the target account) by the CreateShareableSnapshot step, and its lifecycle is owned by the requesting team so it is never deleted by cleanup.",
      "Type": "Choice",
      "Choices": [
        {
          "And": [
            {
              "Variable": "$.restore_snapshot_id",
              "IsPresent": true
            },
            {
              "Not": {
                "Variable": "$.restore_snapshot_id",
                "StringEquals": ""
              }
            }
          ],
          "Next": "SetSuppliedSnapshotCloneRestoreFalse"
        }
      ],
      "Default": "SameAccountCheck"
    },
    "SetSuppliedSnapshotCloneRestoreFalse": {
      "Type": "Pass",
      "Result": "false",
      "ResultPath": "$.clone_restore",
      "Next": "CreateShareableSnapshot"
    },
    "SameAccountCheck": {
      "Comment": "If the target database is in the same account as the source database, we can always use clones. If not, we need to perform additional checks to determine if the cross-account clone method can be used.",
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.source_account_id",
          "StringEqualsPath": "$.target_account_id",
          "Next": "SetCloneRestoreTrue"
        }
      ],
      "Default": "CMKCheck"
    },
    "CMKCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.source_db_info.Payload.config.IsEncryptedWithDefaultKey",
          "BooleanEquals": true,
          "Next": "SetCloneRestoreFalse"
        }
      ],
      "Default": "ForceSnapshotCheck"
    },
    "ForceSnapshotCheck": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.force_snapshot",
          "BooleanEquals": true,
          "Next": "SetCloneRestoreFalse"
        }
      ],
      "Default": "CloneGroupIdPresentCheck"
    },
    "CloneGroupIdPresentCheck": {
      "Comment": "Check if the source database has a clone group ID. If not, it has never been cloned and we can therefore proceed with a cross-account clone. Otherwise, we need to check the target account for existing cross-account clones",
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.source_db_info.Payload.config.CloneGroupId",
          "IsNull": false,
          "Next": "GetExistingCrossAccountClone"
        }
      ],
      "Default": "SetCloneRestoreTrue"
    },
    "GetExistingCrossAccountClone": {
      "Comment": "Determine if there is an existing database in the target account that is a clone of the source database",
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:rds:describeDBClusters",
      "Parameters": {
        "Filters": [
          {
            "Name": "clone-group-id",
            "Values.$": "States.Array($.source_db_info.Payload.config.CloneGroupId)"
          }
        ]
      },
      "Credentials": {
        "RoleArn.$": "States.Format('arn:aws:iam::{}:role/{}', $.target_account_id, $.target_account_role)"
      },
      "ResultPath": "$.cross_account_clones",
      "Retry": [
        {
          "ErrorEquals": ["States.TaskFailed"],
          "IntervalSeconds": 10,
          "MaxAttempts": 3,
          "BackoffRate": 2
        }
      ],
      "Next": "ExistingCrossAccountCloneCheck"
    },
    "ExistingCrossAccountCloneCheck": {
      "Comment": "AWS does not support multiple cross-account clones of the same source database in a given account, so only proceed with a clone if there is no existing clone of the source database in the target account",
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.cross_account_clones.DbClusters[0]",
          "IsPresent": true,
          "Next": "SetCloneRestoreFalse"
        }
      ],
      "Default": "SetCloneRestoreTrue"
    },
    "SetCloneRestoreTrue": {
      "Type": "Pass",
      "Result": "true",
      "ResultPath": "$.clone_restore",
      "Next": "SetDummySnapshotName"
    },
    "SetDummySnapshotName": {
      "Type": "Pass",
      "ResultPath": "$.snapshot_name",
      "Result": "dummy-snapshot-name",
      "Next": "CheckScaleInKafkaConnector"
    },
    "SetCloneRestoreFalse": {
      "Type": "Pass",
      "Result": "false",
      "ResultPath": "$.clone_restore",
      "Next": "CreateShareableSnapshot"
    },
    "CreateShareableSnapshot": {
      "Type": "Task",
      "Resource": "arn:aws:states:::ecs:runTask.waitForTaskToken",
      "TimeoutSeconds": 7200,
      "Parameters": {
        "LaunchType":"FARGATE",
        "Cluster":"${create_shareable_snapshot_fargate_cluster_arn}",
        "TaskDefinition":"${create_shareable_snapshot_fargate_task_definition_arn}",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "Subnets": ${fargate_task_subnets},
            "AssignPublicIp": "DISABLED",
            "SecurityGroups": ["${create_shareable_snapshot_fargate_task_security_group}"]
          }
        },
        "Overrides":{
            "ContainerOverrides": [
              {
                  "Name": "rds-refresh-create-shareable-snapshot",
                  "Environment":[
                    {
                      "Name":"DB_NAME",
                      "Value.$":"$.source_db_name"
                    },
                    {
                      "Name":"DB_TYPE",
                      "Value.$":"$.source_db_info.Payload.db_type"
                    },
                    {
                      "Name":"EXECUTION_ID",
                      "Value.$":"$$.Execution.Id"
                    },
                    {
                      "Name":"SOURCE_ACCOUNT_ID",
                      "Value.$":"$.source_account_id"
                    },
                    {
                      "Name":"TARGET_ACCOUNT_ID",
                      "Value.$":"$.target_account_id"
                    },
                    {
                      "Name":"RESTORE_SNAPSHOT_ID",
                      "Value.$":"$.restore_snapshot_id"
                    },
                    {
                      "Name":"TASK_TOKEN",
                      "Value.$":"$$.Task.Token"
                    }
                  ]
              }
            ]
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "ECS.AmazonECSException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 3,
          "BackoffRate": 3
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": "$.error",
          "Next": "Cleanup"
        }
      ],
      "ResultPath": "$.snapshot_name",
      "Next": "CheckScaleInKafkaConnector"
    },
    "CheckScaleInKafkaConnector" :{
      "Type" : "Choice",
      "Choices" : [
        {
          "Variable" : "$.kafka_connector",
          "IsPresent": true,
          "Next" : "ScaleInKafkaConnector"
        }
      ],
      "Default" : "Restore"
    },
    "ScaleInKafkaConnector" : {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_manage_kafka_connector_name}",
        "Payload": {
          "account_id.$": "$.connector_account_id",
          "bootstrap_servers.$": "$.kafka_bootstrap_servers",
          "cluster_name.$": "$.kafka_connector",
          "role.$": "$.target_account_role",
          "service_name.$": "$.kafka_connector",
          "connector_name.$": "$.connector_name",
          "scale_action": "in",
          "topics.$": "$.kafka_topics"
        }
      },
      "TimeoutSeconds": 60,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 5,
          "BackoffRate": 3
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": "$.error",
          "Next": "Cleanup"
        }
      ],
      "ResultPath": "$.scale_in_kafka_response",
      "Next": "Restore"
    },
    "Restore": {
      "Type": "Task",
      "Resource": "arn:aws:states:::ecs:runTask.waitForTaskToken",
      "TimeoutSeconds": 10800,
      "Parameters": {
        "LaunchType":"FARGATE",
        "Cluster":"${restore_fargate_cluster_arn}",
        "TaskDefinition":"${restore_fargate_task_definition_arn}",
        "NetworkConfiguration": {
          "AwsvpcConfiguration": {
            "Subnets": ${fargate_task_subnets},
            "AssignPublicIp": "DISABLED",
            "SecurityGroups": ["${restore_fargate_task_security_group}"]
          }
        },
        "Overrides":{
            "ContainerOverrides": [
              {
                  "Name": "rds-refresh-restore",
                  "Environment":[
                    {
                      "Name":"DB_NAME",
                      "Value.$":"$.target_db_name"
                    },
                    {
                      "Name": "SOURCE_DB_NAME",
                      "Value.$": "$.source_db_name"
                    },
                    {
                      "Name":"DB_TYPE",
                      "Value.$":"$.target_db_info.Payload.db_type"
                    },
                    {
                      "Name":"EXECUTION_ID",
                      "Value.$":"$$.Execution.Id"
                    },
                    {
                      "Name":"SANITISE_DATA_FUNCTION_NAME",
                      "Value.$":"$.sanitise_data_function_name"
                    },
                    {
                      "Name":"SNAPSHOT_NAME",
                      "Value.$":"$.snapshot_name"
                    },
                    {
                      "Name":"SOURCE_ACCOUNT_ID",
                      "Value.$":"$.source_account_id"
                    },
                    {
                      "Name":"TARGET_ACCOUNT_ID",
                      "Value.$":"$.target_account_id"
                    },
                    {
                      "Name":"TARGET_ACCOUNT_ROLE_NAME",
                      "Value.$":"$.target_account_role"
                    },
                    {
                      "Name":"CLONE_RESTORE",
                      "Value.$":"$.clone_restore"
                    },
                    {
                      "Name":"TASK_TOKEN",
                      "Value.$":"$$.Task.Token"
                    }
                  ]
              }
            ]
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "ECS.AmazonECSException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 3,
          "BackoffRate": 3
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": "$.error",
          "Next": "Cleanup"
        }
      ],
      "ResultPath": null,
      "Next": "Cleanup"
    },
    "Cleanup": {
      "Type": "Pass",
      "Next": "CheckScaleOutKafkaConnector"
    },
    "CheckScaleOutKafkaConnector" :{
      "Type" : "Choice",
      "Choices" : [
        {
          "Variable" : "$.scale_in_kafka_response",
          "IsPresent": true,
          "Next" : "ScaleOutKafkaConnector"
        }
      ],
      "Default" : "CheckDeleteSnapshots"
    },
    "ScaleOutKafkaConnector" : {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_manage_kafka_connector_name}",
        "Payload": {
          "account_id.$": "$.connector_account_id",
          "bootstrap_servers.$": "$.kafka_bootstrap_servers",
          "cluster_name.$": "$.kafka_connector",
          "role.$": "$.target_account_role",
          "service_name.$": "$.kafka_connector",
          "connector_name.$": "$.connector_name",
          "scale_action": "out",
          "task_count.$": "$.scale_in_kafka_response.Payload.original_task_count",
          "topics.$": "$.kafka_topics",
          "wait": false
        }
      },
      "TimeoutSeconds": 60,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 5,
          "BackoffRate": 3
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": null,
          "Next": "CheckDeleteSnapshots"
        }
      ],
      "ResultPath": "$.scale_out_kafka_response",
      "Next": "CheckDeleteSnapshots"
    },
    "CheckDeleteSnapshots": {
        "Type": "Choice",
        "Choices": [
          {
            "Variable": "$.clone_restore",
            "StringEquals": "true",
            "Next": "CheckSnowflakeRequired"
          }
        ],
        "Default": "DeleteIntermediateSnapshotsSourceAccount"
      },
    "DeleteIntermediateSnapshotsSourceAccount": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_delete_intermediate_snapshots_name}",
        "Payload": {
          "db_name.$": "$.source_db_name",
          "db_type.$": "$.source_db_info.Payload.db_type",
          "execution_id.$": "$$.Execution.Id",
          "source_account_id.$": "$.source_account_id",
          "source_account_role": "${source_account_role}"
        }
      },
      "TimeoutSeconds": 900,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 5,
          "BackoffRate": 3
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": null,
          "Next": "DeleteIntermediateSnapshotsTargetAccount"
        }
      ],
      "ResultPath": null,
      "Next": "DeleteIntermediateSnapshotsTargetAccount"
    },
    "DeleteIntermediateSnapshotsTargetAccount": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_delete_intermediate_snapshots_name}",
        "Payload": {
          "db_name.$": "$.source_db_name",
          "db_type.$": "$.source_db_info.Payload.db_type",
          "execution_id.$": "$$.Execution.Id",
          "source_account_id.$": "$.target_account_id",
          "source_account_role.$": "$.target_account_role"
        }
      },
      "TimeoutSeconds": 900,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 60,
          "MaxAttempts": 5,
          "BackoffRate": 3
        }
      ],
      "Catch": [
        {
          "ErrorEquals": ["States.ALL"],
          "ResultPath": null,
          "Next": "CheckForError"
        }
      ],
      "ResultPath": null,
      "Next": "CheckSnowflakeRequired"
    },
    "CheckSnowflakeRequired": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.requires_snowflake_refresh",
          "IsPresent": false,
          "Next": "CheckFivetranSync"
        },
        {
          "Variable": "$.requires_snowflake_refresh",
          "BooleanEquals": true,
          "Next": "RefreshSnowflakeDatabase"
        }
      ],
      "Default": "CheckFivetranSync"
    },
    "RefreshSnowflakeDatabase": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_refresh_snowflake_name}",
        "Payload": {
          "account_id.$": "$.target_account_id",
          "db_name.$": "$.target_db_name"
        }
      },
      "TimeoutSeconds": 300,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 30,
          "MaxAttempts": 3,
          "BackoffRate": 2
        }
      ],
      "ResultPath": "$.snowflake_result",
      "Next": "CheckFivetranSync"
    },
    "CheckFivetranSync": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.requires_fivetran_sync",
          "IsPresent": false,
          "Next": "CheckForError"
        },
        {
          "Variable": "$.requires_fivetran_sync",
          "BooleanEquals": true,
          "Next": "FivetranSync"
        }
      ],
      "Default": "CheckForError"
    },
    "FivetranSync": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "${lambda_fivetran_sync_name}",
        "Payload": {
          "connector_id.$": "$.fivetran_connector_id",
          "historical_sync.$": "$.fivetran_historical_sync"
        }
      },
      "TimeoutSeconds": 300,
      "Retry": [
        {
          "ErrorEquals": [
            "States.Timeout",
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 30,
          "MaxAttempts": 3,
          "BackoffRate": 2
        }
      ],
      "ResultPath": "$.fivetran_result",
      "Next": "CheckForError"
    },
    "CheckForError" :{
      "Type" : "Choice",
      "Choices" : [
        {
          "Variable" : "$.error",
          "IsPresent": true,
          "Next" : "Error"
        }
      ],
      "Default" : "End"
    },
    "Error" : {
      "Type" : "Fail",
      "CausePath": "States.Format('Step function failed in earlier stage. Error: {}', $.error)"
    },
    "End" : {
      "Type" : "Pass",
      "Result": "Step function completed successfully",
      "End": true
    }
  }
}
