コールバックURLを生成するsfn-callback-urlsアプリケーションをStep Functionsで使ってみた
構成
最終的な構成のイメージです。破線で囲ったsfn-callback-urls
以外は手動で作成します。
sfn-callback-urlsデプロイ
サーバレスアプリケーションのコンソールにてsfn-callback-urls
で検索を行います。該当のアプリケーションが表示されたら、アプリケーション名をクリックします。
IAMのチェックを付与し、他の設定はデフォルトのまま「デプロイ」をクリックします。
数分でデプロイが完了します。
裏ではアプリケーションのSAMテンプレートが実行されていますので、CloudFormationのコンソールからも状況を確認することが可能です。
SNS トピックの作成
SNSトピック、サブスクリプションを作成します。 サブスクリプションのプロトコルは「Eメール」で、エンドポイントに承認者のメールアドレスを指定します。トピック名は任意です。作成されたトピックのARNは後ほど利用します。
Lambda Function作成
以下定義でLambda Functionを作成します。
- 関数名…ApprovalEmailsFunction
- ランタイム…Python 3.8
- 実行ロール…基本的な Lambda アクセス権限で新しいロールを作成
IAMロール設定
Lambda Function作成時に作成されたロールに権限を付与します。IAMコンソールで表示するリンクをクリックします。
「インラインポリシーの追加」をクリックします。
2つのインラインポリシーを設定します。
- SNS
- アクション…Publish
- リソース…先程作成したSNSトピックのARN
- Lambda
- アクション…InvokeFunction
- リソース…
sfn-callback-urls
アプリケーションでデプロイされたserverlessrepo-sfn-callback-urls-CreateUrls-xxxxxxxxxxx
のARN
任意のポリシー名を指定し「ポリシーの作成」をクリックします。
コード
Labmda Functionのコンソールに戻り、関数コードに以下を記述します。
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 {}
環境変数に以下を指定し、Lambda Functionを更新して保存します。
- TOPIC_ARN…先程作成したSNSトピックのARN
- CREATE_URLS_FUNCTION…
sfn-callback-urls
アプリケーションでデプロイされたserverlessrepo-sfn-callback-urls-CreateUrls-xxxxxxxxxxx
のARN
ステートマシン作成
IAMロール設定
ステートマシンにアタッチするIAMロールを作成します。
- AWSサービス…StepFunctions
- ポリシー…AWSLambdaRole
- ロール名…ApprovalEmailsStateMachineRole
ステートマシン定義
以下の設定でステートマシンを作成します。
- タイプ…標準
- ステートマシン名…ApprovalEmails
- IAMロール…ApprovalEmailsStateMachineRole
{ "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つのステートを含むステートマシンが作成できました。
以降の動作確認でも触れますが、SendApprovalRequest
ステートでは、waitForTaskToken
を利用しているので、Lambda Function呼び出し後、タスクトークンを受け取るまで後続のステートは実行されません。waitForTaskToken
の詳細については以下を確認ください。
動作確認
最初に呼び出しされるステートSendApprovalRequest
で、name
を受け取っているので以下の入力を与えステートマシンを実行します。
{ "name": "Saka" }
SendApprovalRequestステートの処理
Parameters
フィールドで指定した$$.State.Name
などのコンテキストオブジェクトを渡し、Lambda FunctionApprovalEmailsFunction
が実行されます。
ApprovalEmailsFunction
では渡された値を判定(このステートから呼び出しされるていることを確認)し、承認要求用のURLを生成します。このURLはSNSを通し承認者にメール通知されます。
ApprovalEmailsFunction
呼び出し時にwaitForTaskToken
を付与していますので、処理が完了してもステートマシンにタスクトークンが返されるまで、状態は進行中のままとなります。
なお、ここではタイムアウトを設定していないので、サービスクォータに達するまで1年間タスクトークンを待機します。
承認者の対応
SendApprovalRequest
ステートの処理が完了すると、承認者にメールが届きます。
承認、拒否でURLが異なりますのでいずれかのURLをクリックします。ここでは承認のURLをクリックしました。
SendConfirmationステートの処理
承認者がURLをクリックすると、API Gateway経由でLambda Functionserverlessrepo-sfn-callbac-ProcessCallbackFunction-xxxxxxxxxxxx
が呼び出しされます。このLambda Functionでタスクトークンをステートマシンに返し、SendConfirmation
ステートが実行されます。
SendConfirmation
ステートでも、Lambda FunctionApprovalEmailsFunction
を呼び出しますが、SendApprovalReques
ステートとは異なる値がLambda Functionに渡されます。
このステートから呼び出しされたApprovalEmailsFunction
は、承認、拒否など承認者がクリックしたURLに合わせたメッセージを生成し承認者にメール通知を行います。今回は承認をクリックしていたので、以下のようなメッセージとなりました。
これで、ステートマシンの処理は完了となります。
なお、拒否のURLをクリックすると以下のような動作となります。
さいごに
sfn-callback-urlsアプリケーションをStep Functionsから利用してみました。今回は承認要求用URLの生成にLambda Functionserverlessrepo-sfn-callback-urls-CreateUrls-xxxxxxxxxxx
を呼び出しましたが、API経由でURLを作成するLambda Functionserverlessrepo-sfn-callback-urls-CreateUrlsForApi-xxxxxxxxx
も提供されています。(デプロイされています)業務に合わせたカスタマイズは必要になると思いますが、こちらのアプリケーションを利用することで承認を伴うようなワークフローの作成が捗りそうですね。