この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
構成
最終的な構成のイメージです。破線で囲った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
も提供されています。(デプロイされています)業務に合わせたカスタマイズは必要になると思いますが、こちらのアプリケーションを利用することで承認を伴うようなワークフローの作成が捗りそうですね。