Amazon Web Services ブログ

AWS Step Functions で承認メールにコールバック URL を使用する

iRobot のクラウドロボティクスリサーチサイエンティストで、AWS サーバーレスヒーローでもある Ben Kehoe 氏によるゲスト投稿

AWS Step Functions は、宣言型 Amazon ステートメント言語を使用してプロセスを調整できるサーバーレスワークフローオーケストレーションサービスです。Step Functions のタスクが 15 分以上かかる場合には、AWS Lambda 関数を使用できません。このような場合、Step Functions がコールバックパターンを提供します。このカテゴリでは、承認メールが一般的なユースケースです。

この投稿では、E メール承認ステップで sfn-callback-urls アプリケーションを使用する Step Functions ステートマシンを作成する方法をご紹介します。アプリは AWS Serverless Application Repository で利用できます。ステートマシンは承認/拒否リンクを含む E メールを送信し、後で確認メールを送信します。このステートマシンはユースケースに合わせて簡単に拡張できます。

ソリューションの概要
承認メールには、ユーザーがクリックした際に適切な結果を Step Functions に送り返す URL を含める必要があります。URL は署名済み URL よりも長い期間有効である必要があります。でも、もし今週ユーザーが休暇を取っているとしたら、どうしますか? URL には、トークンの保管と必要なメンテナンスを伴わないのが理想です。幸い、AWS Serverless Application Repository というアプリがあります。

sfn-callback-urls アプリを使用すると、Amazon API Gateway か Lambda 関数のいずれかを呼び出して、1 回限りのコールバック URL を生成できます。各 URL には関連付けられた名前があり、成功したか失敗したかか、あるいはどの出力を Step Functions に送り返すかを決めます。HTTP GET または POST を URL に送信すると、その出力が Step Functions に送信されます。sfn-callback-urls はステートレスで、Webhook で使用するための JSON ボディを持つ POST コールバックもサポートしています。

アプリのデプロイ
まず、sfn-callback-urls サーバーレスアプリをデプロイし、発行する Lambda 関数の ARN を書き留めます。AWS Serverless Application Repository コンソールで、[Show apps that create custom IAM roles or resource policies] をクリックし、sfn-callback-urls を検索します。 アプリケーションにアクセスすることもできます。

[application settings] で、IAM リソースの作成を確認するボックスをクリックします。デフォルトでは、このアプリは KMS キーを作成します。DisableEncryption パラメータを true に設定することで、これを無効にできます。詳しくは、左側にある Readme の Security セクションをお読みください。下方へスクロールし、[Deploy] をクリックします。

デプロイの確認ページで [CreateUrls] を選択します。これで、この機能の Lambda コンソールが開きます。後で必要となりますので、関数 ARN を書き留めておきましょう。
以下を実行して、アプリケーションを作成します。

  1. SNS トピックを作成し、そのトピックに E メールをサブスクライブする。
  2. URL の作成とメール送信を処理する Lambda 関数を作成し、適切なアクセス許可を追加する。
  3. ステートマシンの IAM ロールを作成して、Lambda 関数を呼び出す。
  4. ステートマシンの作成
  5. 実行を開始して、ご自分に E メールを送ってみましょう。

SNS トピックの作成
SNS コンソールで、[Topics]、[Create Topic] の順にクリックします。トピックに ApprovalEmailsTopic という名前を付け、[Create Topic] を選択します。

トピック ARN (arn:aws:sns:us-east-2:012345678912:ApprovalEmailsTopic など) をメモします。
次に、メールを受信するためのサブスクリプションを設定します。[Create subscription] をクリックします。[Protocol] で [Email] を選択し、メールアドレスを入力して、[Create subscription] をクリックします。

確認リンクが記載されたメールが、受信トレイに届くのを待ちます。サブスクリプションを確認し、トピックに発行したメッセージをメールで送信できるようにします。

Lambda 関数の作成
次に、コールバック URL の作成とメールの送信を処理する Lambda 関数を作成します。この投稿ではスペースの制限があるため、1 つのLambda関数を作成して、2 つの別々のステップを完了します。

  • コールバック URL を作成する
  • 承認メールを送信し、後で確認メールを送信する

コードには 2 つを分離する IF ステートメントがあります。このため、ステートマシンが Lambda 関数にどのステートを呼び出しているかを通知する必要があります。ここでは、2 つの異なる Lambda 関数を使用することを推奨します。

Lambda コンソールで Lambda 関数を作成するには、[Create function] をクリックし、ApprovalEmailsFunction という名前を付けて、最新の Python 3 ランタイムを選択します。[Permissions] で、[Create a new Role with basic permissions]、[Create] の順にクリックします。

[Configuration] のところまでスクロールダウンし、アクセス許可を追加します。リンクを選択して、IAM コンソールでロールを確認します。

IAM アクセス許可の追加
IAM コンソールで新しいロールを選択し、[Add inline policy] をクリックします。作成したトピックに sns:Publish のアクセス許可を追加し、sfn-callback-urls CreateUrls 関数 ARN に lambda:InvokeFunction を追加します。

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 関数に設定します。

コードと環境変数を更新後、[Save] をクリックします。
 
ステートマシンの作成
最初に、新しい Lambda 関数を呼び出すことができるステートマシンのロールが必要です。

IAM コンソール で、信頼できるエンティティとして Step Functions を使用し、ロールを作成します。これには AWSLambdaRole ポリシーが必要です。このポリシーは、関数を呼び出すためのアクセスを許可するものです。ロールに ApprovalEmailsStateMachineRole という名前を付けます。

これで、ステートマシンを作成する準備ができました。Step Functions コンソールで、[Create state machine] をクリックし、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
        }
    }
}

このステートマシンには 2 つの状態があります。入力として「name」という 1 つのフィールドを持つ JSON オブジェクトを取得します。各状態は Lambda のタスクです。この投稿を簡潔にするため、両方の状態の機能を 1 つの Lambda 関数にまとめました。状態名を step フィールドとして渡し、実行するコードブロックを関数が選択できるようにします。さまざまなる機能に異な関数を使ったベストプラクティスの場合、このフィールドは必要ありません。

最初の状態である SendApprovalRequest では、name フィールドを持つ入力 JSON オブジェクトが必要です。その名前とステップおよびタスクトークン (コールバックタスクを完了するために必要) をパッケージし、それを使って Lambda 関数を呼び出します。コールバックの一部として受信される出力は、ステートマシンが output フィールド下の出力 JSON オブジェクトに保存します。この出力は、2 番目の状態への入力になります。

2 番目の状態である SendConfirmation はその出力フィールドをステップとともに取得し、関数を再度呼び出します。2 番目の呼び出しはコールバックパターンを使用せず、タスクトークンを含みません。

実行を開始する
この例を実行するには [Start execution] をクリックし、入力を次のような JSON オブジェクトに設定します。
{
  "name": "Ben"
}

SendApprovalRequest 状態を強調表示した実行グラフが表示されます。これは、実行を開始し、タスクトークンが返されるのを待っていることを意味します。受信トレイで、approvereject へのリンクがあるメールを確認してください。リンクをクリックして、ブラウザで応答を承諾したことを示す確認ページを開きます。ステートマシンコンソールで、実行が終了したことが確認できます。また、承認または拒否の確認メールが届きます。

まとめ
この投稿では、AWS Serverless Application Repository の sfn-callback-urls アプリを使用して、承認メールの URL を作成する方法をご紹介しました。これらの E メールを作成および送信し、結果を処理できるシステムを構築する方法も解説しました。さらに大規模なステートマシンの一環としてこのパターンを使用し、独自のワークフローを管理できます。

今回の例は、sfn-callback-urls GitHub リポジトリ の AWS CloudFormation テンプレートとしても利用できます。

Ben Kehoe