亚马逊AWS官方博客

借助 AWS Step Functions 将回调 URL 用于批准电子邮件

本博文是 AWS 无服务器精英人才、iRobot 云机器人技术研究科学家 Ben Kehoe 所写的客座文章

AWS Step Functions 是一种无服务器工作流程编排服务,可让您使用声明性 Amazon States 语言协调流程。如果 Step Functions 任务耗时超过 15 分钟,则无法使用 AWS Lambda 函数 – 在这种情况下,Step Functions 能为我们提供回调模式。批准电子邮件是此类别中的常见的使用案例。

在这篇博文中,我将向您展示如何创建 Step Functions 状态机,以使用 sfn-callback-urls 应用程序完成电子邮件批准步骤。可以在 AWS Serverless Application Repository 中找到该应用程序。该状态机会发送一封包含批准/拒绝链接的电子邮件,随后发送确认电子邮件。您可以轻松针对自己的使用案例扩展此状态机。

解决方案概览
批准电子邮件必须包含 URL,用于在用户单击这些 URL 时将相应结果发送回 Step Functions。该 URL 应该具有较长的有效期,比预先签核的 URL 的有效期更长,因为要考虑用户本周休假的情况。 理想情况下,这不涉及令牌的存储和所需的维护工作。幸运的是,AWS Serverless Application Repository 应用程序可以处理这种情况!

sfn-callback-urls 应用程序允许您通过调用 Amazon API Gateway 或 Lambda 函数生成一次性回调 URL。每个 URL 都有一个关联的名称,表明其成功还是失败,以及应将哪些输出发回给 Step Functions。向 URL 发送 HTTP GET 或 POST 时,会将其输出发送到 Step Functions。sfn-callback-urls 是无状态的,而且它还支持带有 JSON 主体的 POST 回调,以便与 Webhook 配合使用。

部署应用程序
首先,部署 sfn-callback-urls 无服务器应用程序,并记下它公开的 Lambda 函数的 ARN。在 AWS Serverless Application Repository 控制台中,选择显示创建自定义 IAM 角色或资源策略的应用程序,然后搜索 sfn-callback-urls。 您还可以访问该应用程序

应用程序设置下,选中确认创建 IAM 资源的复选框。默认情况下,此应用程序会创建 KMS 密钥。您可以通过将 DisableEncryption 参数设置为 true 来禁用此功能,但首先请阅读左侧自述文件中的“安全性”部分。向下滚动并选择“部署”。

在部署确认页面上,选择 CreateUrls,以打开该函数的 Lambda 控制台。记下函数的 ARN,以便在后面的操作中使用。
执行以下操作,创建应用程序:

  1. 创建一个 SNS 主题并使用您的电子邮件地址订阅该主题。
  2. 创建处理 URL 创建和电子邮件发送的 Lambda 函数,并添加适当的权限。
  3. 为状态机创建 IAM 角色以调用 Lambda 函数。
  4. 创建状态机。
  5. 开始执行并给自己发送一些电子邮件!

创建 SNS 主题
SNS 控制台中,依次选择主题创建主题。将该主题命名为 ApprovalEmailsTopic,然后选择创建主题

记下主题 ARN,例如 arn:aws:sns:us-east-2:012345678912:ApprovalEmailsTopic。
现在,设置订阅以接收电子邮件。选择创建订阅。对于协议,请选择电子邮件,输入一个电子邮件地址,然后选择创建订阅

等待包含确认链接的电子邮件发送到您的收件箱。它用于确认订阅,允许通过电子邮件将发布到该主题的消息发送给您。

创建 Lambda 函数
现在创建 Lambda 函数,以便处理回调 URL 的创建和电子邮件发送。在这篇简短的博文中,我们只要创建一个 Lambda 函数来完成两个单独的步骤:

  • 创建回调 URL
  • 发送批准电子邮件,然后发送确认电子邮件

代码中有一个 if 语句将两者分开,状态机需要告知 Lambda 函数哪个状态正在调用该函数。这里的最佳实践是分别使用两个单独的 Lambda 函数。

要在 Lambda 控制台中创建 Lambda 函数,请选择创建函数,将其命名为 ApprovalEmailsFunction,然后选择最新的 Python 3 运行时。在权限下,选择创建具有基本权限的新角色,然后选择创建

向下滚动到配置部分来添加权限。选择链接以在 IAM 控制台中查看角色。

添加 IAM 权限
在 IAM 控制台中,选择新角色,然后选择添加内联策略。将 sns:Publish 权限添加到您创建的主题,将 lambda:InvokeFunction 权限添加到 sfn-callback-urls CreateUrls 函数 ARN。

回到 Lambda 控制台,在函数中使用以下代码:

import json, os, boto3
def lambda_handler(event, context):
    print('Event:', json.dumps(event))
    # Switch between the two blocks of code to run
    # This is normally in separate functions
    if event['step'] == 'SendApprovalRequest':
        print('Calling sfn-callback-urls app')
        input = {
            # Step Functions gives us this callback token
            # sfn-callback-urls needs it to be able to complete the task
            "token": event['token'],
            "actions": [
                # The approval action that transfers the name to the output
                {
                    "name": "approve",
                    "type": "success",
                    "output": {
                        # watch for re-use of this field below
                        "name_in_output": event['name_in_input']
                    }
                },
                # The rejection action that names the rejecter
                {
                    "name": "reject",
                    "type": "failure",
                    "error": "rejected",
                    "cause": event['name_in_input'] + " rejected it"
                }
            ]
        }
        response = boto3.client('lambda').invoke(
            FunctionName=os.environ['CREATE_URLS_FUNCTION'],
            Payload=json.dumps(input)
        )
        urls = json.loads(response['Payload'].read())['urls']
        print('Got urls:', urls)

        # Compose email
        email_subject = 'Step Functions example approval request'

        email_body = """Hello {name},
        Click below (these could be better in HTML emails):

        Approve:
        {approve}

        Reject:
        {reject}
        """.format(
            name=event['name_in_input'],
            approve=urls['approve'],
            reject=urls['reject']
        )
    elif event['step'] == 'SendConfirmation':
        # Compose email
        email_subject = 'Step Functions example complete'

        if 'Error' in event['output']:
            email_body = """Hello,
            Your task was rejected: {cause}
            """.format(
                cause=event['output']['Cause']
            )
        else:
            email_body = """Hello {name},
            Your task is complete.
            """.format(
                name=event['output']['name_in_output']
            )
    else:
        raise ValueError

    print('Sending email:', email_body)
    boto3.client('sns').publish(
        TopicArn=os.environ['TOPIC_ARN'],
        Subject=email_subject,
        Message=email_body
    )
    print('done')
    return {}

现在,将以下环境变量 TOPIC_ARN 和 CREATE_URLS FUNCTION 设置为主题的 ARN 和前面提到的 sfn-callback-urls 函数。

更新代码和环境变量后,选择保存
 
创建状态机
首先您需要一个状态机角色,并假设该角色可以调用新的 Lambda 函数。

IAM 控制台中,创建一个角色,并将 Step Functions 用作其可信实体。这需要 AWSLambdaRole 策略,以便为其提供调用您的函数的权限。将该角色命名为 ApprovalEmailsStateMachineRole

现在,您可以创建状态机了。在 Step Functions 控制台中,选择创建状态机,将其命名为 ApprovalEmails,并使用以下代码:

{
    "Version": "1.0",
    "StartAt": "SendApprovalRequest",
    "States": {
        "SendApprovalRequest": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
            "Parameters": {
                "FunctionName": "ApprovalEmailsFunction",
                "Payload": {
                    "step.$": "$$.State.Name",
                    "name_in_input.$": "$.name",
                    "token.$": "$$.Task.Token"
                }
            },
            "ResultPath": "$.output",
            "Next": "SendConfirmation",
            "Catch": [
                {
                    "ErrorEquals": [ "rejected" ],
                    "ResultPath": "$.output",
                    "Next": "SendConfirmation"
                }
            ]
        },
        "SendConfirmation": {
            "Type": "Task",
            "Resource": "arn:aws:states:::lambda:invoke",
            "Parameters": {
                "FunctionName": "ApprovalEmailsFunction",
                "Payload": {
                    "step.$": "$$.State.Name",
                    "output.$": "$.output"
                }
            },
            "End": true
        }
    }
}

该状态机有两种状态。它获取一个带字段“name”的 JSON 对象作为输入。每个状态都是一个 Lambda 任务。为使这篇博文简明扼要,我在单个 Lambda 函数中结合了两种状态的功能。您将状态名称作为 step 字段传递给函数,以允许该函数选择要运行的代码块。根据使用不同函数负责不同任务的最佳实践,此字段不是必需的。

第一种状态 SendApprovalRequest 需要带有 name 字段的输入 JSON 对象。它将该名称与步骤和任务令牌(完成回调任务所必须)打包在一起,并使用它调用 Lambda 函数。无论通过回调接收怎样的输出,状态机都将其存储在 output 字段下的输出 JSON 对象中。随后,该输出会成为第二个状态的输入。

第二个状态 SendConfirmation 将该输出字段与该步骤一起使用并再次调用该函数。第二个调用不使用回调模式,也不涉及任务令牌。

开始执行
要运行该示例,请选择开始执行,并将输入设置为 JSON 对象,如下所示:
{
  "name": "Ben"
}

您会看到突出显示 SendApprovalRequest 状态的执行图表。这意味着它已经启动,并且正在等待返回任务令牌。检查收件箱中是否有包含批准拒绝链接的电子邮件。选择链接后,浏览器会打开一个确认页面,表明已接受您的回复。在状态机控制台中,您会看到执行已完成,并且您还会收到一封用于批准或拒绝的确认电子邮件。

小结
在这篇博文中,我演示了如何使用 AWS Serverless Application Repository 中的 sfn-callback-urls 应用程序为批准电子邮件创建 URL。我还向您展示了如何构建系统,来创建和发送这些电子邮件并处理结果。此模式可用作更大规模的状态机的一部分,用于管理您自己的工作流程。

此示例还在 sfn-callback-urls GitHub 代码库中作为 AWS CloudFormation 模板提供。

Ben Kehoe