🎵

[StepFunctions]実行中のステートマシン内から実行ARN、もしくは実行名を取得したかった話

2024/07/25に公開

はじめに

前回に引き続き、GitHub Copilotに聞いて解決した問題になります。
よろしければ、前回の記事も合わせてご覧ください。

https://zenn.dev/goals_techblog/articles/2221037d5e628c

やりたいこと

今回も記事のタイトル通りですが、、、
実行中のStepFunctionsの実行ARN(executionArn)、もしくは実行名(name)を取得しようとしました。

背景

やりたいことの背景としては、既存運用では以下の対応(後述のGitHub Copilot提案の方法2のパターン)をしていました。
しかし、StepFucntionsを実行時に手動で引数を埋めるという運用が一手間かかったため、改善できないか調べていました。

  1. StepFunctionsの実行時の引数に、実行名を手動で入力して取得
  2. 同StepFucntionsから呼び出されるLambdaに対して、手動入力した実行名を引数に渡して実行
  3. Lambdaのロジック内で、実行名を元にAWSアカウントID等を付け加えて、実行ARNを生成
  4. 実行ARNを元に、実行しているStepFucntionsのURLを生成
  5. 生成したURLを含んだSlack通知を実施

LambdaからSlack通知の実装については、難しいことはしていないため、本記事では割愛します。
参考までに、非常に分かりやすいハンズオンを公開していた mn87 様の記事を貼り付けておきます。

https://zenn.dev/mn87/articles/f102140a0540bf

事前に確認したポイント

独自実装等はせずに、用意されているStepFucntionsのアクションでやりたいことが実現できないか確認しました。

しかし、アクションで渡せる引数がstateMachineArn程度しか無く、現状では実行中のARNを取得する方法は見つかりませんでした。

https://docs.aws.amazon.com/step-functions/latest/apireference/API_Operations.html

GitHub Copilotに聞いてみた

そこで、前回記事同様に、GitHub Copilot Chatに聞いてみました。

imamura-ko-0314
AWS StepFunctionsにて、実行中のステートマシン内から、その実行中のexecutionArnを取得する方法はありますか?

GitHub Copilot
AWS Step Functionsでは、実行中のステートマシン内から直接executionArnを取得するためのネイティブな方法は提供されていません。
しかし、いくつかの方法で間接的にexecutionArnを取得することが可能です。

方法1: Lambda関数を使用する
AWS Lambda関数を使用して、contextオブジェクトからexecutionArnを取得し、それをステートマシンに返す方法があります。

Lambda関数の例

import json

def lambda_handler(event, context):
    execution_arn = context.invoked_function_arn
    return {
        'executionArn': execution_arn,
        'input': event
    }

Step Functionsの定義例

{
  "Comment": "A description of my state machine",
  "StartAt": "GetExecutionArn",
  "States": {
    "GetExecutionArn": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:YOUR_LAMBDA_FUNCTION",
      "End": true
    }
  }
}

方法2: ステートマシンの入力に含める
ステートマシンを開始する際に、executionArnを入力パラメータとして含めることもできます。
これにより、ステートマシン内の各ステートでexecutionArnにアクセスできます。

ステートマシンの開始例

aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:REGION:ACCOUNT_ID:stateMachine:YOUR_STATE_MACHINE \
  --input '{"executionArn": "arn:aws:states:REGION:ACCOUNT_ID:execution:YOUR_STATE_MACHINE:EXECUTION_NAME"}'

Step Functionsの定義例

{
  "Comment": "A description of my state machine",
  "StartAt": "YourFirstState",
  "States": {
    "YourFirstState": {
      "Type": "Pass",
      "ResultPath": "$.executionArn",
      "Next": "NextState"
    },
    "NextState": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:YOUR_LAMBDA_FUNCTION",
      "End": true
    }
  }
}

これらの方法を使用することで、実行中のステートマシン内からexecutionArnを取得し、必要な処理を行うことができます。

やはり、実行中のStepFunctions内から直接executionArnを取得するための方法はないようでした。

そして、代替案として提案された方法2は、既存の運用ままのものでした。
ただし方法1については、やりたいことの背景にも記載した通り、LambdaでSlack通知する中でexecutionArnが欲しかったので、要望と合致する上に手間が減るようなBestPracticeのようでした!!

実際に動かしてみた

Lambdaのcontextオブジェクトを活用するように、Slack通知用のLambdaを実装しました。
以下のようなサンプルコードで、意図通りに値が取得できることが確認できました!

sample_slack_notify_lambda.py
import json
import urllib.request
import os

WEBHOOK_URL = 'SLACK_WEBHOOK_URL_XXXX'
CHANNEL_NAME = 'SLACK_CHANNEL_NAME_XXXX'

def post_slack(param, context):
    execution_arn = context.invoked_function_arn
    webhook_url = WEBHOOK_URL
    channel = CHANNEL_NAME
    env_name = os.getenv('ENV_NAME')

    payload = get_payload(param, env_name, channel, execution_arn)

    data = json.dumps(payload).encode('utf-8')
    method = 'POST'
    headers = {'Content-Type': 'application/json'}
    request = urllib.request.Request(webhook_url, data = data, method = method, headers = headers)
    with urllib.request.urlopen(request) as response:
        return response.read().decode('utf-8')

def get_payload(param, env_name, channel, execution_arn):
    subject = 'ERROR: '
    message = 'Environment: {}'.format(env_name)

    if 'states' in execution_arn:
        # 本LambdaがStepFunctionsからの実行だった場合
        sf_task_url = 'https://ap-northeast-1.console.aws.amazon.com/states/home?region=ap-northeast-1#/v2/executions/details/{}'.format(execution_arn)
        message = message + '\nSF Execution Link: {}'.format(sf_task_url)

    payload = {
        'attachments':[{
            'fields':[
                {
                   'title': subject,
                   'value': message
                }
             ]
        }],
        'channel': channel
    }
    return payload

def lambda_handler(event, context):
    post_slack(event, context)

まとめ

反省点として、、、
どうしても1人の頭で考えていると、挙動の改善をするためにStepFunctions側の挙動にこだわってしまっていて、凝り固まった考えになっていました。

やりたいことを実現するために、別のソリューションも提案してくれるGitHub Copilot最高!!

また、最後に今回活用したPythonのLambda contextオブジェクトについてのリファレンスがあったため、こちらを貼り付けておきます。

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-context.html

Goals Tech Blog

Discussion